Go2 泛型设计草案更新 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
songtianyi
V2EX    程序员

Go2 泛型设计草案更新

  •  
      songtianyi 2020-12-09 18:50:44 +08:00 3130 次点击
    这是一个创建于 1802 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Go2 泛型设计草案更新

    作者: songtianyi create@2020/12/03

    前言

    18 年的时候,go team 发布了 Go2 的几个新特性的草案,其中包括呼声较高的泛型,当时写了一篇文章做了介绍。最近 Go team 对泛型的设计草案进行了较大的改动,有必要更新下这个改动并分享出来。

    contracts

    在 18 年释出的草案中,是使用 contract 来约束泛型的类型参数(type parameters)的,最新的草案放弃了这种做法, 用已有的概念 interface 代替。 在继续之前,先来熟悉 type parameter 这个概念:

    Generic code is code that is written using types that will be specified later. Each unspecified type is called a type parameter. When running the generic code, the type parameter will be set to a type argument.

    好,继续。回顾下 contract 形式的例子:

    contract stringer(T) { T String() string } func Stringify(type T stringer)(s []T) (ret []string) { for _, v := range s { ret = append(ret, v.String()) // now valid } return ret } strSlice = []string{} 

    上述代码约束了入参 s 的类型 T 必须是实现了 String 函数的类型

    interface

    那么改用 interface 之后怎么做?

    // Stringer is a type constraint that requires the type argument to have // a String method and permits the generic function to call String. // The String method should return a string representation of the value. type Stringer interface { String() string } // Stringify calls the String method on each element of s, // and returns the results. func Stringify[T Stringer](s []T) (ret []string) { for _, v := range s { ret = append(ret, v.String()) } return ret } 

    上述代码,使用 Stringer interface 来约束入参 s 的类型 T 必须是实现了 String() string 函数的类型。 除了使用自定义的 interface 来约束之外,Go 内置了 any 来指明入参是可以为任意类型的, 当我们不需要约束的时候可以使用 any 来维持写法的一致性, any 相当于 interface{}

    // Print prints the elements of any slice. // Print has a type parameter T and has a single (non-type) // parameter s which is a slice of that type parameter. func Print[T any](s []T) { for _, v := range s { fmt.Println(v) } } 

    interface 我们经常会用到,是一个已经非常熟悉的概念,而且使用 interface 可以避免不必要的重复定义的情况。以上面的 Stringer 为例,对 Stringify 函数,如果使用 contract 来进行约束,我们需要定义:

    // 约束 contract stringer_c(T) { T String() string } // Stringer 接口 type Stringer interface { String() string } // 入参 s 被约束为实现了 String() string 函数的类型 func Stringify[T stringer_c](s []T) (ret []string) { for _, v := range s { ret = append(ret, v.String()) } return ret } // 实现了 String() string 的结构体 type IStringer struct { v string } // String() string 实现 func (i *IStringer) String() string { return v } var i_stringer IStringer Stringfy(i_stringer) // 合法入参 

    从上面的代码可以看出, stringer_c contract 其实和 Stringer interface 是重复的。 这和 Stringer interface 的定义其实是重复的。

    看到这里是不是觉得这个改动还是很棒的?相对 contract 来说,interface 更好理解,有时候也可以省掉重复的定义。 但是,interface 只能定义函数,因此,我们只能使用 interface 来约束 T 必须实现的函数,而不能约束 T 所能支持的运算。 使用 contract 来约束类型参数所支持的运算符的例子:

    // comparable contract contract ordered(t T) { t < t } func Smallest[T ordered](s []T) T { r := s[0] // panic if slice is empty for _, v := range s[1:] { if v < r { // OK r = v } } return r } 

    很方便。 但使用 interface 就没那么方便了:

    package constraints // Ordered is a type constraint that matches any ordered type. // An ordered type is one that supports the <, <=, >, and >= operators. type Ordered interface { type int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr, float32, float64, string } // Smallest returns the smallest element in a slice. // It panics if the slice is empty. func Smallest[T constraints.Ordered](s []T) T { r := s[0] // panics if slice is empty for _, v := range s[1:] { if v < r { r = v } } return r } 

    要写一大堆... 心里一阵 mmp... 先别慌!

    Ordered interface 里列出来的类型是 Ordered 约束可以接受的类型参数。由此看来,针对运算符的约束写起来变的更复杂了,幸运的是,go 会内置常用的约束, 不用我们自己来写. 而且,约束是可以组合的:

    // ComparableHasher is a type constraint that matches all // comparable types with a Hash method. type ComparableHasher interface { comparable Hash() uintptr } 

    上述代码是一个约束,它约束类型参数必须是可比较的,而且实现了 Hash() uintptr 函数。

    // StringableSignedInteger is a type constraint that matches any // type that is both 1) defined as a signed integer type; // 2) has a String method. type StringableSignedInteger interface { type int, int8, int16, int32, int64 String() string } 

    类似地,可以将可接受的类型列表(type list)和函数约束放在一起。

    讲到这里,关于泛型改动的核心内容已经讲完了,更复杂的用法可以查看文档 go2draft-type-parameters.

    个人认为,这个改动是一个比较成功的改动,没有引入新的概念,通过内置一些约束,支持约束组合来方便开发者。

    17 条回复    2020-12-10 10:33:35 +08:00
    zxCoder
        1
    zxCoder  
       2020-12-09 18:54:10 +08:00
    加个泛型有这么复杂吗? 好奇怪
    wysnylc
        2
    wysnylc  
       2020-12-09 18:56:30 +08:00
    @zxCoder #1 有,因为最初版本的设计是不加泛型
    从 2010 年就有泛型的提案,直到现在也没转正所以你懂 google 那群人的意思了吧
    songtianyi
        3
    songtianyi  
    OP
       2020-12-09 18:58:40 +08:00
    内容更正:
    Stringfy(i_stringer) // 合法入参 Stringfy([]IStringer{i_stringer}) // 合法入
    luob
        4
    luob  
       2020-12-09 19:03:36 +08:00 via iPhone   3
    “同意加泛型的点赞,不同意加泛型的点踩”
    “我同意加泛型,可我对每个泛型草案都点了踩”
    “那您快请加入到 go 核心团队来”
    12101111
        5
    12101111  
       2020-12-09 19:07:58 +08:00
    和 rust 的 trait 一样啊
    Jirajine
        6
    Jirajine  
       2020-12-09 19:09:21 +08:00 via Android
    >不能约束 T 所能支持的运算
    T 支持 xx 运算和实现 xx 方法本来就是一回事,为什么非要搞两个概念呢?
    lewis89
        7
    lewis89  
       2020-12-09 19:10:09 +08:00
    倒是给个泛型发版的时间啊,都这么久了 还没发布 2.0 的意思
    zxCoder
        8
    zxCoder  
       2020-12-09 19:10:36 +08:00
    @wysnylc 泛型这东西到底有啥坑吗? 按道理搞一个编程语言出来不应该是取其他语言之精华去其糟粕
    songtianyi
        9
    songtianyi  
    OP
       2020-12-09 19:17:01 +08:00
    @Jirajine 因为修改后的方案是 使用 已有的概念,interface 已有的概念里是没有这些东东的。
    ```
    type Ordered interface {
    type int, int8, int16, int32, int64,
    uint, uint8, uint16, uint32, uint64, uintptr,
    float32, float64,
    string
    }
    ```
    所以以递进的形式来讲。事实上,像你说的,本来就是一回事。
    12101111
        10
    12101111  
       2020-12-09 19:19:02 +08:00
    我很好奇未来 go 的类型系统会不会进一步演进, 比如 generic associated types, higher-kinded types
    victor
        11
    victor  
       2020-12-09 19:19:12 +08:00
    希望 2030 年后再添加泛型
    songtianyi
        12
    songtianyi  
    OP
       2020-12-09 19:23:48 +08:00
    @12101111 我觉得不会,类型系统是一个语言的最主要的特征之一,改多了就不是 go 了。
    songtianyi
        13
    songtianyi  
    OP
       2020-12-09 19:30:21 +08:00
    @12101111 是挺像的。
    aloxaf
        14
    aloxaf  
       2020-12-09 19:36:37 +08:00
    @12101111
    你这个好奇也太离谱了点。你不如好奇美国什么时候会走社会主义道路,我觉得这都比 Go 加 HKT 要现实……
    zjsxwc
        15
    zjsxwc  
       2020-12-09 20:12:31 +08:00 via Android
    第二种方法,用 interface 更加容易理解,我站第二种。
    laball
        16
    laball  
       2020-12-10 09:31:56 +08:00
    感觉语言特性发展过程中,有一种弊病,就是别人已经用了这种方式,我就得不一样,不然显得是在抄作业。
    我就想说,还 TM 嫌括号不够多嘛,加个特性,加一组括号,这是要干啥。。。
    songtianyi
        17
    songtianyi  
    OP
       2020-12-10 10:33:35 +08:00
    @laball 看了样例代码,确实眼花缭乱的 .
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2606 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 07:50 PVG 15:50 LAX 23:50 JFK 02:50
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86