本议题采用 实时线上会议 / 论坛回帖 结合的模式,可直接回复本贴或参与线上会议讨论,线上会议时间为 2023年5月28日20点至21点,会议视频:
会议中涉及大小接口转换时多次口误,统一修正为:“小接口向大接口可直接转换;大接口向小接口转换需通过类型断言进行动态检查。”
本议题采用 实时线上会议 / 论坛回帖 结合的模式,可直接回复本贴或参与线上会议讨论,线上会议时间为 2023年5月28日20点至21点,会议视频:
会议中涉及大小接口转换时多次口误,统一修正为:“小接口向大接口可直接转换;大接口向小接口转换需通过类型断言进行动态检查。”
会议中演示的接口、匿名类型例子如下:
向空接口赋值:
type T1 struct {
a: i32
}
type T2 struct {
b: i32
}
func main() {
var ival: i32 = 777
doConcreteType(ival)
doConcreteType("你好凹语言")
v1 := T1{a: 13}
doConcreteType(v1)
v2 := T2{b: 42}
doConcreteType(v2)
}
func doConcreteType(i: interface{}) {
//接口到具体类型断言
switch c := i.(type){
case i32:
println("i32: ", c)
case string:
println("string: ", c)
case T1:
println("T1, T1.a==", c.a)
case T2:
println("T2, T2.b==", c.b)
}
}
向非空接口赋值,以及调用接口方法:
type T1 struct {
a: i32
}
type I1 interface {
f()
}
func T1.f() {
println("This is T1.f, this.a==", this.a)
}
func main() {
v1 := T1{a: 13}
var i1: I1 = &v1 //具体类型到具名接口
i1.f() //接口调用
doConcreteType(i1)
i1.f()
v1.f()
p, ok := i1.(*T1) //双返回值(带成功标志)类型断言
if ok{
p.f()
}
}
func doConcreteType(i: interface{}) {
//接口到具体类型断言
switch c := i.(type){
case *T1:
println("*T1")
c.a *= 2
}
}
通过类型断言在一个接口上查询该接口内的对象实现的另一个接口:
type T1 struct {
a: i32
}
type T2 struct {
b: i32
}
type I1 interface {
f()
}
type I2 interface {
f()
f2()
}
func T1.f() {
println("This is T1.f, this.a==", this.a)
}
func T2.f(){
println("This is T2.f, this.b==", this.b)
}
func T2.f2(){
println("This is T2.f2, this.b==", this.b)
}
func main() {
v1 := T1{a: 13}
var i1: I1 = &v1 //具体类型到具名接口
doConcreteType(i1)
i1.f() //接口调用
v2 := T2{b: 42}
var i2: I2 = &v2 //具体类型到具名接口
doConcreteType(i2)
i2.f2()
i1 = i2
i1.f()
v2.b = 123
i1.f()
i2 = i1.(I2)
i2.f2()
//i1 = &v1
//i2 = i1.(I2) //接口互转,由于v2未实现I2,这会触发异常
}
func doConcreteType(i: interface{}) {
//接口到具体类型断言
switch c := i.(type){
case *T1:
println("*T1")
c.a *= 2
case *T2:
println("*T2")
c.b *= 2
}
}
匿名接口,以及接口包含:
type T2 struct {
b: i32
}
type I1 interface {
f()
}
type I2 interface {
I1
f2()
}
func T2.f(){
println("This is T2.f, this.b==", this.b)
}
func T2.f2(){
println("This is T2.f2, this.b==", this.b)
}
func main() {
v2 := T2{b: 42}
var i2: I2 = &v2 //具体类型到具名接口
i2.f2()
var anoni: interface{ f() } = &v2 //具体类型到匿名接口
v2.b = 13
anoni.f()
i2 = anoni.(I2) //匿名接口向具名接口转换
i2.f2()
}
匿名结构体:
var Info: struct{
name: string
age: i32
}
func main() {
//全局匿名结构体变量
Info.name = "张三"
Info.age = 88
println(Info.name, " ", Info.age) //张三 88
//局部匿名结构体变量
k := struct {name: string; age: i32}{"李四", 66}
println(k.name, " ", k.age) //李四 66
Info = k
println(Info.name, " ", Info.age) //李四 66
}
v0.7.0增加了单元测试能力(_examples/hello):
func TestFailed {
ok := true
assert(ok)
ok = false
assert(ok, "message")
}
以 Test 为开头,函数中通过 assert 断言。assert 有2中形式:
func assert(ok: bool)
func assert(ok: bool, message: string)
assert 只在测试环境预定义。然后执行测试:
$ go run ../../main.go test .
---- myapp.TestFailed
assert failed: message (test_zz.wa:8:8)
FAIL myapp 4.609s
exit status 1
此外也支持 Output 测试(这是 fmt 包的例子):
func TestOK_output {
println("abc 1233")
// Output:
// abc 123
}
执行 fmt 包测试:
$ go run main.go test fmt
---- fmt.TestOK_output
expect = "abc 123", got = "abc 1233"
FAIL fmt 3.13s
exit status 1
关于 Output,@Ending 建议用 #
开头,但是 message 部分因为涉及多行建议支持多行注释的形式。
关于 Output,@zhaopuming 建议考虑 assert_output
内置函数。不过参数有可能是运行时才能确定,对函数行为需要补丁规则。
另外还有 bench 功能还没有实现。
欢迎大家提供反馈意见。
请问接口的“大”和“小”的含义是?听到空接口是最“大”的接口,是指一个接口包含方法的个数越多就越“小”吗?关于“小接口向大接口可直接转换”,现在是如何判断这两个接口哪个大呢?
如果 T2.f 被实现了两次,会报错么?
关于 assert 的 message,一直觉得有点别扭,因为是在不符预期时才显示,性质上像是报错信息,而代码里看起来像是对该测试的说明。不知有没有考虑过其他形式的测试形式,比如在函数定义之前列出测试用例:
# 输入(3) 输出(”i32: 3“)
# 输入(T1{a: 13}) 输出(”T1, T1.a==13“)
func doConcreteType(i: interface{}) {
......
}
关于 支持 Output 测试,如果是针对 TestOK_output
的输出,写在函数体外面看起来会否更明确?如:
// Output:
// abc 123
func TestOK_output {
println("abc 1233")
}
另外,建议尽量将设计与实现分开考虑。这样可以避免”现在不方便实现“而过早放弃掉合理的需求。
当向接口的方法集中添加方法时,满足该接口(既实现了该接口所有方法)的对象数量有减少的趋势。
接口大小是个相对概念,只用于比较两个具有包含关系的接口,比较严谨的描述是这样:
若接口 A 的方法集合为 S1,接口 B 的方法集合为 S2,当 S1∈S2 时,我们称 “B 是相对于 A 的小接口”,既 “A 是相对于 B 的大接口”。
“小接口向大接口直接转换”是在编译时完成的,因为编译时可以根据上述定义判断接口 A 和 接口 B 的方法集是否存在包含关系,实现了小接口 B 的对象,显然满足大接口 A,因此 B 向 A 转换可以直接进行,而不用动态查询。
与C系语言不同,凹中的对象不可以拥有两个同名方法——哪怕这两个方法的签名不同。T2.f()被实现两次会报错。