0%

读Go泛型提案有感

原本我以为Go添加泛型就加个type注释就可以,刚读了一遍Go generic proposal,发现要考虑的很多

提案里用C++类比,很久没写,不怎么熟悉,我用Java举例子

如下Go代码

1
2
3
4
5
6
7
// This function is INVALID.
func Stringify(type T)(s []T) (ret []string) {
for _, v := range s {
ret = append(ret, v.String()) // INVALID
}
return ret
}

这份代码问题在于,v只是T类型,编译系统无法确定T类型含有String()方法,在Go中,全部的字段都会在编译时进行解析绑定,所以Go不允许上面的写法

这是泛型系统普遍存在的问题

如下Java代码

1
2
3
4
5
6
7
public static <T> String[] Stringify1(T[] s) {
String[] ret = null;
for(int idx = 0 ; idx <= s.length; idx ++) {
ret[idx] = s[idx].String();
}
return ret;
}

你的String()方法根本过不了编译,无法解析String字段
那Java是如何解决这个问题的呢?有界类型参数(Bounded Type Parameters)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class StringClass<T> extends ArrayList<T> {
public String String() {
return null;
}
}

public class App {
public static <T extends StringClass<Integer>> String[] Stringify(T[] s) {
String[] ret = null;
for(int idx = 0 ; idx <= s.length; idx ++) {
ret[idx] = s[idx].String();
}
return ret;
}
}

通过extends硬性限定T的类型范围,这样调用String()时可正确解析。
如果Go支持泛型,那必须顺带着解决 Bounded Type Parameters 的问题,标准的解决问题引入问题解决问题循环

Go借助现有的interface来做类型约束(type constraint)

1
2
3
4
5
6
7
8
9
type Stringer interface {
String() string
}
func Stringify(type T Stringer)(s []T) (ret []string) {
for _, v := range s {
ret = append(ret, v.String())
}
return ret
}
  1. 运算符约束
1
2
3
4
5
6
7
8
9
10
11
由于T是不定类型,所以无法比较
// This function is INVALID.
func Smallest(type T)(s []T) T {
r := s[0] // panic if slice is empty
for _, v := range s[1:] {
if v < r { // INVALID
r = v
}
}
return r
}

那Java呢?根据我所学,Java禁止不定类型的比较,C++支持,但需要运算符重载(operator overloading)

Java中原始类型(primitive type)都被封装到对象中,就连Integer比较都需要拆箱来提取int,更何谈直接比较Object

Go从设计上就没有OOP,struct传递完全可以用pointer这个生值,而这些值天生就是可以被比较的,不能禁止

这个要说一下

这个代码s也是传递引用,我在这里不想讨论值传递和引用传递,明白的人自然能看明白

1
public void print(String s) {}

也就是s也是一个数值,不是对象,不过Java在你调用s之前会解引用(*s).balabalabala

1
2
3
4
5
6
7
8
type s struct {

}
func (this *s) doSth(){}
func print(this *s)){
// Go这里能调用,是由于编译器发现没有歧义,会帮助你转换成(*this).balabala调用
this.doSth()
}

Go官方FAQ

Why not use the syntax F like C++ and Java?
When parsing code within a function, such as v := F, at the point of seeing the < it’s ambiguous whether we are seeing a type instantiation or an expression using the < operator. Resolving that requires effectively unbounded lookahead. In general we strive to keep the Go parser efficient.