为什么 []MyType 不是 []interface{} 的子类型?
在一个平凡的工作日,我尝试将一个自定义类型的 slice([]MyType)赋值给一个接收 []interface{} 类型参数的函数。然而 IDE 却告诉我不能这样使用。
乍一看,interface{}(Go 语言中的空接口类型)是 MyType 的父类型,那么 []interface{} 自然也应该是 []MyType 的父类型,合情合理。于是我将这个报错发到了公司的“迷惑代码大赏”群。在回帖中,我发现事情比我想象的更加复杂:如果 []interface{} 是 []MyType 的父类型,那么以下代码应当成立:
func G() {
v := []int{1,2,3}
F(v)
fmt.Println(v)
}
func F(v []interface{}) {
// string is a subtype of interface{}, so this should be valid
v[0] = "Oops"
}
然而我们显然可以看出这段代码是不成立的,那么一定是哪里出了问题。
我们知道,根据里氏代换原则,子类可以当成父类使用。假如 B 是 A 的子类,那么用 A 的地方一定可以用 B 来替换。这里的替换,包含了 A 所具有的一切方法(method)。当我们谈论类与对象时,这非常好理解,大家也都对此非常熟悉。不过,函数也是一种类型。当 A 和 B 用作函数的参数和返回值时,函数的继承关系如何呢?也就是说,func() A、func() B 及 func (A)、func(B) 具有怎样的继承关系呢?
让我们先来讨论前者,即 func() A、func() B 具有怎样的继承关系。如果使用 func() A 的地方一定可以用 func() B 替换,而使用 func() B 的地方不能用 func() A 替换,那么 func() A 就是 func() B 的父类了。看看事实是否如此吧!
“使用”,意味着将其作为参数参与运算,不论其在语法上的表象是作为函数的参数还是表达式中的操作数(operand)。因为一个 a+b
的表达式,也可以表示为 Add(a, b)
。因此我们就将其作为函数的参数,这样比较直观。
另外,由于 A、B 过于抽象,我们使用一个现实中的例子来继续后续的讨论: io.Reader
是 Go 语言中人尽皆知的一个接口,它具有一个 Read() 方法。 *bytes.Buffer
是 io.Reader
的子类型。
以下显然是成立的:
func F() *bytes.Buffer {
}
func Use(f func() io.Reader) {
reader := f()
reader.Read()
}
func main() {
Use(F)
}
以下是不成立的:
func F() io.Reader {
}
func Use(f func() *bytes.Buffer) {
buffer := f()
_ = buffer.String()
}
func main() {
Use(F)
// Cannot use 'F' (type func() io.Reader) as the type func() *bytes.Buffer
}
因此,我们发现 func() *bytes.Buffer 是 func() io.Reader 的子类型。即父子类型的关系,在类型作为函数返回值时依然保持。如果 A 是 B 的父类型,则函数 func() A
是函数 func() B
的父类型。
然后我们来看看当类型作为函数的参数时,父子类型的关系是否依然保持。以下是成立的:
func F(r io.Reader) {
r.Read([]byte{})
}
func Use(f func(*bytes.Buffer)) {
b := new(bytes.Buffer)
f(b)
}
func main() {
Use(F) // 会报错,因为 Go 的类型是“不变”(invariant)的。但是原理上是没问题的
}
以下是不成立的:
func F(r *bytes.Buffer) {
_ = r.String()
}
func Use(f func(io.Reader)) {
r := csv.NewReader()
f(r)
}
func main() {
Use(F)
}
因此,我们发现 func(io.Reader) 是 func(*bytes.Buffer) 的子类型。即父子类型的关系,在类型作为函数参数时发生逆转。如果 A 是 B 的父类型,则函数 func(A)
是函数 func(B)
的子类型。
下面我们开始证明为什么 []MyType 不能当 []interface{} 用,即为什么 []MyType 不是 []interface{} 的子类型了:
众所周知,slice 具有如下两个基本操作:
as := make([]A, 10)
a := as[0] // func Get(as []A, i int) A
as[1] = a // func Set(as []A, i int, a A)
抛去语法的外衣,可以看为一个 Get 函数和一个 Set 函数。
若要让 []MyType 是 []interface{} 的子类型,则 []MyType 必须实现 []interface{} 的所有方法。即:
func Get(as []MyType, i int) MyType
必须是func Get(as []interface{}, i int) interface{}
的子类 1️⃣func Set(as []MyType, i int, a MyType)
必须是func Set(as []interface{}, i int, a interface{})
的子类 2️⃣
又已知 MyType 是 interface{} 的子类,那么:
f(interface{})
必定是f(MyType)
的子类 3️⃣f()MyType
必定是f() interface{}
的子类 4️⃣
即:
Get() MyType
必定是Get() interface{}
的子类 5️⃣Set(interface{})
必定是Set(MyType)
的子类(而这与上面的1️⃣矛盾)
通过以上形式化证明我们发现:[]MyType 不是 []interface{} 的子类。
但是,这仅仅是因为 MyType 出现在了 Set 的参数中而引起的。也就是说,如果有个只读的切片类型,表示为 ro []MyType
,那么 ro []MyType
会是 ro []interface{}
的子类型。
其他语言是如何解决这个问题的呢?欢迎评论区补充。