凹语言2023年1-5月进展通报及讨论会

本议题采用 实时线上会议 / 论坛回帖 结合的模式,可直接回复本贴或参与线上会议讨论,线上会议时间为 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 功能还没有实现。

欢迎大家提供反馈意见。

1 个赞

请问接口的“大”和“小”的含义是?听到空接口是最“大”的接口,是指一个接口包含方法的个数越多就越“小”吗?关于“小接口向大接口可直接转换”,现在是如何判断这两个接口哪个大呢?

如果 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()被实现两次会报错。

1 个赞