标签 Java 下的文章

系统学习Go语言,有这几本书就够了!

img{512x368}

1. Go语言的发展现状

如果从2007年9月20日那个下午三个“程序员大佬”在谷歌总部的一间办公室里进行的一次有关设计一门新编程语言的讨论算起,那么Go语言已经度过了自己的13个年头了。

img{512x368}

Robert Griesemer、Rob Pike和Ken Thompson

如果从2009年11月10日Go语言正式开源发布算起,Go语言也即将迎来自己的第11个生日

img{512x368}

2020年,Go联合创始人Rob Pike在专访中也认可了Go确实已成为云基础架构的语言。在Go即将迎来自己的11个生日的时候,Hacker News有人发起了“Go已超过10岁了,你觉得这门语言如何?”的提问,收到了广泛的关注和回答。国内媒体将这些问答整理后得到的结论是:“人生苦短,我要换Go”

Stackoverflow官博11月2日发表的《Go语言有哪些优点?探讨导致Go语言日益流行的特征 》一文对Go语言的发展趋势描述的贴切:Go语言就像爬行的藤蔓,虽缓慢,但却逐渐占据了开发世界。它正以一种郁郁葱葱的并且在许多方面都很优越的编程能力覆盖着在它之前出现的所有事物
img{512x368}

不管你是否承认,Go在IT就业市场已经成为事实上的“香饽饽”之一,就像一贯不激进的慕课网也在今年双11打出了下面的专题:

img{512x368}

上车,任何时间都不晚! 那么怎么才能踏上Go这一强大且稳健前行的车呢?和其他主流编程语言一样,上车的必经之路:看书

2. 市面上的Go书籍为何这么少

和C、C++、Java、Python等编程语言在市面上的书籍数量相比,Go流行于市面(大陆)上的图书似乎少了很多。其原因笔者觉得有如下几点:

1) 年轻

我们来看看上述几门主流编程语言的诞生时间:

  • java 1995

  • c 1972

  • c++ 1983

  • python 1991

对于很多IT从业者来说,这些语言诞生的时候他们还没出生呢。而2009年末才正式发布的Go和“最年轻”的java之间还有14年的“年龄差”。

Go在国内真正开始快速流行起来大致在2015年第一届GopherChina大会(2015.4月)之后,当时的Go是1.4版本)。同一年下半年发布的Go 1.5实现自举并让GC延迟大幅下降,这引爆了Go在国内的流行。一批又一批程序员成为Gopher,在大厂、初创实践着Go语言。但知识和技能的沉淀和总结需要时间,相信再有5年,国内作者出版的Go语言相关书籍会像雨后春笋版出现在大家的书架上。

2)以品类代名词的身份占据的“领域”还少

提到Web,人们想到的是Java spring;提到深度学习、机器学习、人工智能,人们想到的是python和tensorflow;提到比特币,嵌入式,人们想到的是C;提到游戏,人们想到的是C++;提到前端,人们想到的是Javascript。这些语言在这些垂直领域早早以杀手级框架入场,使得它们成为了这一领域的“品类代名词”,因此与该垂直领域相关的技术书籍都会采用作为该领域“品类代名词”的编程语言编写书中示例等,这样的书也就会被归类为这类语言方面的书籍。

Go语言诞生晚,入场也较晚。Go虽然通过缓慢的“爬行”,覆盖了一些领域并占据优势地位,但还不能说已经成为了该领域的“品类代名词”,比如:云原生、API、微服务、区块链等,因此被垂直领域书籍关联的机会也不像上面那几门语言多。

同时,由于Go“自带电池”,基于Go标准库我们可以实现大部分功能特性,无需依赖过多框架。即便依赖框架,框架本身也不复杂,很少以“某某框架”为主题编写一本技术书籍,这方面远远无法媲美Java和Spring这对“黄金组合”。

3) 引进国外优秀作品需要时间

相对于国内,国外关于Go语言的作品要多不少,但引进国外图书资料需要时机以及时间(找译者翻译)。

3. 系统学习Go语言的书籍列表TOP 5

笔者接触Go语言较早,Go语言相关的中外文书籍几乎都通读过一遍(经典好书读过可不止一遍哦)。Go语言比较简单,如果单单从系统掌握这门语言的角度来看,阅读下面基本书籍就足够了。如果你要学习某些垂直领域的Go应用和技巧,那么期待我后续对垂直领域Go书籍/资料的推荐吧^_^。

这里参考“天下足球”TOP10栏目的方式推荐我心目中掌握Go语言必读的五大好书(每项满分为5分)!

第五名:《The Way To Go》 – Go语言百科全书

img{512x368}

《The Way To Go》是我早期学习Go语言时最喜欢翻看的一本书。该书成书于2012年3月,恰逢Go 1.0版本刚刚发布,作者承诺书中代码均可在Go 1.0版本上编译通过并运行。该书分为4个部分:

  • 为什么学习Go以及Go环境安装入门

  • Go语言核心语法

  • Go高级用法(读写、错误处理、单元测试、并发编程、socket与web编程等)

  • Go应用(常见陷阱、语言应用模式、从性能考量的代码编写建议、现实中的Go应用等)

每部分的每个章节都很精彩,这本书也是目前见到的最全面详实的讲解Go语言的书籍了,我称之为Gopher们的第一本“Go百科全书”

该书作者Ivo Balbaert想必大多数人都不曾耳闻。为了写本文,我特地研究了一下他的作品以及出版时间,发现这个技术作者是很会“抢先机”并且眼光独到。他总是能发现市面刚出现不久但却很有潜力的编程语言并在其他人了解该门语言之前,就编写出类似“The way to Go”这样的为早期语言接纳者提供的详实资料,包括JuliaRust等。在很多人还不知道这些语言名字的时候,他就已经开始学习这些语言,并为这些语言编写出质量很高的“百科全书”式的书籍。

很遗憾,这本书没有中文版。这可能是由于本书出版太早,等国内出版社意识到要引进Go语言方面的书籍时,这本书使用的Go版本又太老了,虽然本书中绝大部分例子依然可以在今天最新的Go编译器下通过编译并运行起来。不过无闻在github上发起了这本书的中译版项目:https://github.com/Unknwon/the-way-to-go_ZH_CN,感兴趣的gopher可以去在线或下载阅读。
此书虽棒,但毕竟年头“久远”,我只能委屈它一下了,将它列在第五位,下面是其各个指数的评分:

  • 作者名气指数:3

  • 关注度指数:3

  • 内容实用指数:4

  • 经典指数:4

总分:14

第四名:《Go 101》 – Go语言规范全方位解读

img{512x368}

这是一本在国外人气和关注度比在国内高的中国人编写的英文书,当然也是有中文版的。

如果仅从书名中的101去判断,你很大可能会认为这仅仅是一本讲解Go入门基础的书,但这本书的内容可远远不止入门这么简单。这本书可大致分为三个部分:

  • Go语法基础

  • Go类型系统与运行时实现

  • 以专题(topic)形式阐述的Go特性、技巧与实践模式

除了第一部分算是101范畴,其余两个部分都是Go语言的高级话题,也是要精通Go必须要掌握的“知识点”。并且,结合Go语言规范,作者对每个知识点的阐述都细致入微并结合大量示例辅助说明。我们知道有关C和C++语言,市面上有一些由语言作者或标准规范委员会成员编写的annotated或rationale书籍(语言参考手册或标准解读),Go 101这本书也可以理解为Go语言的标准解读或参考手册

Go 101这本书是开源电子书,其作者也在国外一些支持自出版的服务商那里做了付费数字出版。这使得这本书相对于其他纸板书有着另外一个优势:与时俱进。在作者的不断努力下,该书的知识点更新基本保持与Go的演化同步,目前其内容已经覆盖了最新的Go 1.15版本

该书作者为国内资深工程师老貘,他花费三年时间“呕心沥血”完成此书并免费奉献给Go社区,值得大家为其大大的点赞!
下面是本书推荐指数的评分:

  • 作者名气指数:3

  • 关注度指数:4

  • 内容实用指数:4

  • 经典指数:4

总分:15

第三名:《Go语言学习笔记》 – Go源码剖析与实现原理探索

img{512x368}

这是一本在国内影响力很大和关注度较高的作品。一来其作者雨痕老师是国内资深工程师,也是2015年第一届GopherChina大会讲师;二来,该作品的前期版本是以开源电子书的形式风险给国内Go社区的;三来,作者在Go源码剖析方便可谓之条理清晰,细致入微。

2016年《Go语言学习笔记》纸版书出版,该书覆盖了当时最新的Go 1.5版本,Go 1.5版本在Go语言演化历史中的分量极高,它不仅实现了Go自举,还让Go GC的延迟下降到绝大多数应用可以将其应用到生产的程度。本书整体上分为两大部分:

  • Go语言详解:以短平快、捞干的来的风格对Go语言语法做了说明,能用示例说明的,绝不用文字做过多修饰。

  • Go源码剖析:这是本书精华,也是最受Gopher关注的部分。这部分对Go运行时神秘的内存分配、垃圾回收、并发调度、channel和defer的实现原理、syn.Pool的实现原理做了细致的源码剖析与原理总结。

随着Go语言演化,其语言和运行时实现一直在变化,但Go 1.5版本的实现是后续版本的基础,因此这本书的剖析非常值得每位Gopher阅读。从雨痕老师的github上最新消息来看,他似乎在编写新版Go语言学习笔记,基于Go 1.12版本,剖析源码是枯燥繁琐的,期待新版Go学习笔记早日与Gopher们见面。
下面是本书各个指数的评分:

  • 作者名气指数:4

  • 关注度指数:4

  • 内容实用指数:4

  • 经典指数:4

总分:16

第二名:《Go语言实战》 – 实战系列(in action)经典之作,紧扣Go语言的精华

img{512x368}

Manning出版社出版的“实战系列(xx in action)”一直是程序员心中高质量和经典的代名词。在出版Go语言实战方面,该出版社也是丝毫不敢怠慢,邀请了Go社区知名的三名明星级作者联合撰写了该书的内容。这三位作者分别是:

  • 威廉·肯尼迪 (William Kennedy) – 知名Go培训师,培训机构Ardan Labs的联合创始人,”Ultimate Go”培训的策划实施者。

  • 布赖恩·克特森 (Brian Ketelsen) – 世界上最知名的Go技术大会 – GopherCon大会的联合发起人和组织者,GopherAcademy创立者,现微软Azure工程师

  • 埃里克·圣马丁 (Erik St.Martin) – 世界上最知名的Go技术大会 – GopherCon大会的联合发起人和组织者

本书并不是大部头,而是薄薄的一本(中文版才200多页),因此你不要期望从本书得到百科全书一样的阅读感。本书的作者们显然也没有想将其写成面面俱到的作品,而是直击要点,即挑出Go语言和其他语言相比与众不同的特点进行着重讲解,这些特点构成了本书的结构框架:

  • 入门:快速上手搭建、编写、运行一个go程序

  • 语法:数组(作为一个类型而存在)、切片和map

  • Go类型系统的与众不同:方法、接口、嵌入类型

  • Go的拿手好戏:并发及并发模式

  • 标准库常用包:log、marshal/unmarshal、io(Reader和Writer)

  • 原生支持的测试

读完这本书,你就掌握了Go语言的精髓之处,这迎合了多数gopher的内心需求。本书中文版译者Googol Lee也是Go圈子里的资深gopher,翻译质量上乘。

下面对本书各个指数的评分:

  • 作者名气指数:5

  • 关注度指数:5

  • 内容实用指数:4

  • 经典指数:4

总分:18

第一名:《Go程序设计语言》 – 人手一本的Go语言“圣经”

如果说由Brian W. KernighanDennis M. Ritchie联合编写的《The C Programming Language》(也称K&R C)是C程序员(甚至是所有程序员)心目中的“圣经”的话,

img{512x368}

那么同样由Brian W. Kernighan(K)参与编写的《The Go Programming Language》(也称tgpl)就是Go程序员心目中的“圣经”。

img{512x368}

本书模仿并致敬“The C Programming Language”的经典结构,从一个”hello, world”示例开始带领大家开启Go语言之旅。第二章程序结构是Go语言这个“游乐园”的向导图,了解它之后,我们就会迫不及待地奔向各个“景点”细致参观。Go语言规范中的所有“景点”在本书中都被覆盖到了,并且由浅入深,循序渐进:从基础数据类型到复合数据类型、从函数、方法到接口、从创新的并发goroutine到传统的基于共享变量的并发,从包、工具链到测试,从反射到低级编程(unsafe包)。作者行文十分精炼,字字珠玑,这与《The C Programming Language》的风格保持了高度的一致。书中的示例在浅显易懂的同时,又极具实用性并突出Go语言的特点(比如:并发web爬虫、并发非阻塞缓存等)。

读完本书后,你会有一种爱不释手,马上还要从头再读一遍的感觉,也许这就是“圣经”的魅力!

本书出版于2015年10月26日,也是既当年中旬Go 1.5这个里程碑版本发布后,Go社区的又一重大历史事件!并且Brian W. Kernighan老爷子的影响力让更多程序员加入到Go阵营,这也或多或少促成了Go成为下一个年度,即2016年年度TIOBE最佳编程语言。能得到Brian W. Kernighan老爷子青睐的编程语言只有C和Go,这也是Go的幸运。当然了如果老爷子是被Rob Pike或Ken Thompson通过私人关系邀请写书的,那就另当别论了,当然这纯属臆测,别当真^_^。

这本书的另一名作者Alan A. A. Donovan也并非等闲之辈,他是Go核心开发团队的成员,专注于Go工具链方面的开发。

现在唯一遗憾的是Brian W. Kernighan老爷子年事已高,不知道Go加入泛型后老爷子是否还有精力更新这本圣经。

该书中文版由七牛团队翻译,总体质量是不错的。建议Gopher们人手购置一本圣经“供奉”起来!^_^

下面对本书各个指数的评分:

  • 作者名气指数:5

  • 关注度指数:5

  • 内容实用指数:5

  • 经典指数:5

总分:20

4. 小结

Go书籍绝非“汗牛充栋”,预计Go增加泛型表达力增强后,市面上会有更多的技术书籍出炉。上面的某些经典也许还会出新版。而市面上Go书籍不多从另外一角度也可以理解成Go语言在国内还有巨大的发展空间与潜力。

努力吧,Gopher们!


只有写书者,才能体会到写者的艰辛!Go专栏:《改善Go语言编程质量的50个有效实践》也是我努力了一年多才打磨雕琢出来的心血之作。自从上线后,收到大家的热烈关注和好评!现在恰逢双11慕课大促,欢迎有意愿在Go这条技术路线上进阶的朋友们订阅,在学习过程中欢迎随时反馈和交流!

img{512x368}

我的网课“Kubernetes实战:高可用集群搭建、配置、运维与应用”在慕课网热卖中!欢迎小伙伴们学习支持!双十一慕课网优惠空前!别错过机会哦!

img{512x368}
微博:https://weibo.com/bigwhite20xx
微信公众号:iamtonybai
博客:tonybai.com
github: https://github.com/bigwhite

微信赞赏:
img{512x368}

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

Go泛型真的要来了!最早在Go 1.17版本支持

Go官博今晨发表了Go核心团队两位大神Ian Lance Taylor和Go语言之父之一的Robert Griesemer撰写的文章“The Next Step for Generics”,该文介绍了Go泛型(Go Generics)的最新进展和未来计划。

2019年中旬,在Go 1.13版本发布前夕的GopherCon 2019大会上,Ian Lance Taylor代表Go核心团队做了有关Go泛型进展的介绍。自那以后,Go团队对原先的Go Generics技术草案做了进一步精化,并编写了相关工具让社区gopher体验满足这份设计的Go generics语法,返回建议和意见。经过一年多的思考、讨论、反馈与实践,Go核心团队决定在这份旧设计的基础上另起炉灶,撰写了一份Go Generics的新技术提案:“Type Parameters”。与上一份提案最大的不同在于使用扩展的interface类型替代“Contract”用于对类型参数的约束。

parametric polymorphism((形式)参数多态)是Go此版泛型设计的基本思想。和Go设计思想一致,这种参数多态并不是通过像面向对象语言那种子类型的层次体系实现的,而是通过显式定义结构化的约束实现的。基于这种设计思想,该设计不支持模板元编程(template metaprogramming)和编译期运算。

注意:虽然都称为泛型(generics),但是Go中的泛型(generics)仅是用于狭义地表达带有类型参数(type parameter)的函数或类型,这与其他编程语言中的泛型(generics)在含义上有相似性,但不完全相同。

从目前的情况来看,该版设计十分接近于最终接受的方案,因此作为Go语言鼓吹者这里就和大家一起看看最早将于Go 1.17版本(2021年8月)中加入的Go泛型支持究竟是什么样子的。由于目前关于Go泛型的资料仅限于这份设计文档以及一些关于这份设计的讨论贴,本文内容均来自这些资料。另外最终加入Go的泛型很可能与目前设计文档中提到的有所差异,请各位小伙伴们了解。

1. 通过为type和function增加类型参数(type parameters)的方式实现泛型

Go的泛型主要体现在类型和函数的定义上。

  • 泛型函数(generic function)

Go提案中将带有类型参数(type parameters)的函数称为泛型函数,比如:

func PrintSlice(type T)(s []T) {
    for _, v := range s {
        fmt.Printf("%v ", v)
    }
    fmt.Print("\n")
}

其中,函数名PrintSlice与函数参数列表之间的type T即为类型参数列表。顾名思义,该函数用于打印元素类型为T的切片中的所有元素。使用该函数的时候,除了要传入要打印的切片实参外,还需要为类型参数传入实参(一个类型名),这个过程称为泛型函数的实例化。见下面例子:

// https://go2goplay.golang.org/p/rDbio9c4AQI
package main

import "fmt"

func PrintSlice(type T)(s []T) {
    for _, v := range s {
        fmt.Printf("%v ", v)
    }
    fmt.Print("\n")
}

func main() {
    PrintSlice(int)([]int{1, 2, 3, 4, 5})
    PrintSlice(float64)([]float64{1.01, 2.02, 3.03, 4.04, 5.05})
    PrintSlice(string)([]string{"one", "two", "three", "four", "five"})
}

运行该示例:

1 2 3 4 5
1.01 2.02 3.03 4.04 5.05
one two three four five

但是这种每次都显式指定类型参数实参的使用方式显然有些复杂繁琐,给开发人员带来心智负担和不好的体验。Go编译器是聪明的,大多数使用泛型函数的场景下,编译器都会根据函数参数列表传入的实参类型自动推导出类型参数的实参类型(type inference)。比如将上面例子改为下面这样,程序依然可以输出正确的结果。

// https://go2goplay.golang.org/p/UgHqZ7g4rbo
package main

import "fmt"

func PrintSlice(type T)(s []T) {
    for _, v := range s {
        fmt.Printf("%v ", v)
    }
    fmt.Print("\n")
}

func main() {
    PrintSlice([]int{1, 2, 3, 4, 5})
    PrintSlice([]float64{1.01, 2.02, 3.03, 4.04, 5.05})
    PrintSlice([]string{"one", "two", "three", "four", "five"})
}
  • 泛型类型(generic type)

Go提案中将带有类型参数(type parameters)的类型定义称为泛型类型,比如我们定义一个底层类型为切片类型的新类型:Vector:

type Vector(type T) []T

该Vector(切片)类型中的元素类型为T。和泛型函数一样,使用泛型类型时,我们首先要对其进行实例化,即显式为类型参数赋一个实参值(一个类型名):

//https://go2goplay.golang.org/p/tIZN2if1Wxo

package main

import "fmt"

func PrintSlice(type T)(s []T) {
    for _, v := range s {
        fmt.Printf("%v ", v)
    }
    fmt.Print("\n")
}

type Vector(type T) []T

func main() {
    var vs = Vector(int){1, 2, 3, 4, 5}
    PrintSlice(vs)
}

泛型类型的实例化是必须显式为类型参数传参的,编译器无法自行做类型推导。如果将上面例子中main函数改为如下实现方式:

func main() {
    var vs = Vector{1, 2, 3, 4, 5}
    PrintSlice(vs)
}

则Go编译器会报如下错误:

type checking failed for main
prog.go2:15:11: cannot use generic type Vector(type T) without instantiation

这个错误的意思就是:未实例化(instantiation)的泛型类型Vector(type T)无法使用。

2. 通过扩展了的interface类型对类型参数进行约束和限制

1) 对泛型函数中类型参数的约束与限制

有了泛型函数,我们来实现一个“万能”加法函数:

// https://go2goplay.golang.org/p/t0vXI6heUrT
package main

import "fmt"

func Add(type T)(a, b T) T {
    return a + b
}

func main() {
    c := Add(5, 6)
    fmt.Println(c)
}

运行上述示例:

type checking failed for main
prog.go2:6:9: invalid operation: operator + not defined for a (variable of type T)

什么情况!这么简单的一个函数,Go编译器居然报了这个错误:类型参数T未定义“+”这个操作符运算

在此版Go泛型设计中,泛型函数只能使用类型参数所能实例化出的任意类型都能支持的操作。比如上述Add函数的类型参数type T没有任何约束,它可以被实例化为任何类型。那么这些实例化后的类型是否都支持“+”操作符运算呢?显然不是。因此,编译器针对示例代码中的第六行报了错!

对于像上面Add函数那样的没有任何约束的类型参数实例,Go允许对其进行的操作包括:

  • 声明这些类型的变量;
  • 使用相同类型的值为这些变量赋值;
  • 将这些类型的变量以实参形式传给函数或从作为函数返回值;
  • 取这些变量的地址;
  • 将这些类型的值转换或赋值给interface{}类型变量;
  • 通过类型断言将一个接口值赋值给这类类型的变量;
  • 在type switch块中作为一个case分支;
  • 定义和使用由该类型组成的复合类型,比如:元素类型为该类型的切片;
  • 将该类型传递给一些内置函数,比如new。

那么,我们要让上面的Add函数通过编译器的检查,我们就需要限制其类型参数所能实例化出的类型的范围。比如:仅允许实例化为底层类型(underlying type)为整型类型的类型。上一版Go泛型设计中使用Contract来定义对类型参数的约束,不过由于Contract与interface在概念范畴上有交集,让Gopher们十分困惑,于是在新版泛型设计中,Contract这个关键字被移除了,取而代之的是语法扩展了的interface,即我们使用interface类型来修饰类型参数以实现对其可实例化出的类型集合的约束。我们来看下面例子:

// https://go2goplay.golang.org/p/kMxZI2vIsk-
package main

import "fmt"

type PlusableInteger interface {
    type int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64
}

func Add(type T PlusableInteger)(a, b T) T {
    return a + b
}

func main() {
    c := Add(5, 6)
    fmt.Println(c)
}

运行该示例:

11

如果我们在main函数中写下如下代码:

    f := Add(3.65, 7.23)
    fmt.Println(f)

我们将得到如下编译错误:

type checking failed for main
prog.go2:20:7: float64 does not satisfy PlusableInteger (float64 not found in int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64)

我们看到:该提案扩展了interface语法,新增了类型列表(type list)表达方式,专用于对类型参数进行约束。以该示例为例,如果编译器通过类型推导得到的类型在PlusableInteger这个接口定义的类型列表(type list)中,那么编译器将允许这个类型参数实例化;否则就像Add(3.65, 7.23)那样,推导出的类型为float64,该类型不在PlusableInteger这个接口定义的类型列表(type list)中,那么类型参数实例化将报错!

注意:定义中带有类型列表的接口将无法用作接口变量类型,比如下面这个示例:

// https://go2goplay.golang.org/p/RchnTw73VMo
package main

type PlusableInteger interface {
    type int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64
}

func main() {
    var n int = 6
    var i PlusableInteger
    i = n
    _ = i
}

编译器会报如下错误:

type checking failed for main
prog.go2:9:8: interface type for variable cannot contain type constraints (int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64)

我们还可以用interface的原生语义对类型参数进行约束,看下面例子:

// https://go2goplay.golang.org/p/hyTbglTLoIn
package main

import (
    "fmt"
    "strconv"
)

type StringInt int

func (i StringInt) String() string {
    return strconv.Itoa(int(i))
}

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
}

func main() {
    fmt.Println(Stringify([]StringInt{1, 2, 3, 4, 5}))
}

运行该示例:

[1 2 3 4 5]

如果我们在main函数中写下如下代码:

func main() {
    fmt.Println(Stringify([]int{1, 2, 3, 4, 5}))
}

那么我们将得到下面的编译器错误输出:

type checking failed for main
prog.go2:27:2: int does not satisfy Stringer (missing method String)

我们看到:只有实现了Stringer接口的类型才会被允许作为实参传递给Stringify泛型函数的类型参数并成功实例化。

我们还可以结合interface的类型列表(type list)和方法列表一起对类型参数进行约束,看下面示例:

// https://go2goplay.golang.org/p/tchwW6mPL7_d
package main

import (
    "fmt"
    "strconv"
)

type StringInt int

func (i StringInt) String() string {
    return strconv.Itoa(int(i))
}

type SignedIntStringer interface {
    type int, int8, int16, int32, int64
    String() string
}

func Stringify(type T SignedIntStringer)(s []T) (ret []string) {
    for _, v := range s {
        ret = append(ret, v.String())
    }
    return ret
}

func main() {
    fmt.Println(Stringify([]StringInt{1, 2, 3, 4, 5}))
}

在该示例中,用于对泛型函数的类型参数进行约束的SignedIntStringer接口既包含了类型列表,也包含方法列表,这样类型参数的实参类型既要在SignedIntStringer的类型列表中,也要实现了SignedIntStringer的String方法。

如果我们将上面的StringInt的底层类型改为uint:

type StringInt uint

那么我们将得到下面的编译器错误输出:

type checking failed for main
prog.go2:27:14: StringInt does not satisfy SignedIntStringer (uint not found in int, int8, int16, int32, int64)

2) 引入comparable预定义类型约束

由于Go泛型设计选择了不支持运算操作符重载,因此,我们即便对interface做了语法扩展,依然无法表达类型是否支持==!=。为了解决这个表达问题,这份新设计提案中引入了一个新的预定义类型约束:comparable。我们看下面例子:

// https://go2goplay.golang.org/p/tea39NqwZGC
package main

import (
    "fmt"
)

// Index returns the index of x in s, or -1 if not found.
func Index(type T comparable)(s []T, x T) int {
    for i, v := range s {
        // v and x are type T, which has the comparable
        // constraint, so we can use == here.
        if v == x {
            return i
        }
    }
    return -1
}

type Foo struct {
    a string
    b int
}

func main() {
    fmt.Println(Index([]int{1, 2, 3, 4, 5}, 3))
    fmt.Println(Index([]string{"a", "b", "c", "d", "e"}, "d"))
    pos := Index(
        []Foo{
            Foo{"a", 1},
            Foo{"b", 2},
            Foo{"c", 3},
            Foo{"d", 4},
            Foo{"e", 5},
        }, Foo{"b", 2})
    fmt.Println(pos)
}

运行该示例:

2
3
1

我们看到Go的原生支持比较的类型,诸如整型、字符串以及由这些类型组成的复合类型(如结构体)均可以直接作为实参传给由comparable约束的类型参数。comparable可以看成一个由Go编译器特殊处理的、包含由所有内置可比较类型组成的type list的interface类型。我们可以将其嵌入到其他作为约束的接口类型定义中:

type ComparableStringer interface {
    comparable
    String() string
}

只有支持比较的类型且实现了String方法,才能满足ComparableStringer的约束。

3) 对泛型类型中类型参数的约束

和对泛型函数中类型参数的约束方法一样,我们也可以对泛型类型的类型参数以同样方法做同样的约束,看下面例子:

// https://go2goplay.golang.org/p/O-YpTcW-tPu

// Package set implements sets of any comparable type.
package main

// Set is a set of values.
type Set(type T comparable) map[T]struct{}

// Make returns a set of some element type.
func Make(type T comparable)() Set(T) {
    return make(Set(T))
}

// Add adds v to the set s.
// If v is already in s this has no effect.
func (s Set(T)) Add(v T) {
    s[v] = struct{}{}
}

// Delete removes v from the set s.
// If v is not in s this has no effect.
func (s Set(T)) Delete(v T) {
    delete(s, v)
}

// Contains reports whether v is in s.
func (s Set(T)) Contains(v T) bool {
    _, ok := s[v]
    return ok
}

// Len reports the number of elements in s.
func (s Set(T)) Len() int {
    return len(s)
}

// Iterate invokes f on each element of s.
// It's OK for f to call the Delete method.
func (s Set(T)) Iterate(f func(T)) {
    for v := range s {
        f(v)
    }
}

func main() {
    s := Make(int)()

    // Add the value 1,11,111 to the set s.
    s.Add(1)
    s.Add(11)
    s.Add(111)

    // Check that s does not contain the value 11.
    if s.Contains(11) {
        println("the set contains 11")
    }
}

运行该示例:

the set contains 11

这个示例定义了一个数据结构:Set。该Set中的元素是有约束的:必须支持可比较。对应到代码中,我们用comparable作为泛型类型Set的类型参数的约束。

4) 关于泛型类型的方法

泛型类型和普通类型一样,也可以定义自己的方法。但泛型类型的方法目前不支持除泛型类型自身的类型参数之外的其他类型参数了。我们看下面例子:

// https://go2goplay.golang.org/p/JipsxG7jeCN

// Package set implements sets of any comparable type.
package main

// Set is a set of values.
type Set(type T comparable) map[T]struct{}

// Make returns a set of some element type.
func Make(type T comparable)() Set(T) {
    return make(Set(T))
}

// Add adds v to the set s.
// If v is already in s this has no effect.
func (s Set(T)) Add(v T) {
    s[v] = struct{}{}
}

func (s Set(T)) Method1(type P)(v T, p P) {

}

func main() {
    s := Make(int)()
    s.Add(1)
    s.Method1(10, 20)
}

在这个示例中,我们新定义的Method1除了在参数列表中使用泛型类型Set的类型参数T之外,又接受了一个类型参数P。执行该示例:

type checking failed for main
prog.go2:18:24: methods cannot have type parameters

我们看到编译器给出错误:泛型类型的方法不能再有其他类型参数。目前提案仅是暂时不支持额外的类型参数(如果支持,会让语言规范和实现都变得异常复杂),Go核心团队也会听取社区反馈的意见,直到大家都认为支持额外类型参数是有必要的,那么后续会重新添加。

5) type *T Constraint

上面我们一直采用的对类型参数的约束形式是:

type T Constraint

假设调用泛型函数时某类型A要作为T的实参传入,A必须实现Constraint(接口)。

如果我们将上面对类型参数的约束形式改为:

type *T Constraint

那么这将意味着类型A要作为T的实参传入,*A必须满足Constraint(接口)。并且Constraint中的所有方法(如果有的话)都仅能通过*A实例调用。我们来看下面示例:

// https://go2goplay.golang.org/p/g3cwgguCmUo
package main

import (
    "fmt"
    "strconv"
)

type Setter interface {
    Set(string)
}

func FromStrings(type *T Setter)(s []string) []T {
    result := make([]T, len(s))
    for i, v := range s {
        result[i].Set(v)
    }
    return result
}

// Settable is a integer type that can be set from a string.
type Settable int

// Set sets the value of *p from a string.
func (p *Settable) Set(s string) {
    i, _ := strconv.Atoi(s) // real code should not ignore the error
    *p = Settable(i)
}

func main() {
    nums := FromStrings(Settable)([]string{"1", "2"})
    fmt.Println(nums)
}

运行该示例:

[1 2]

我们看到Settable的方法集合是空的,而*Settable的方法集合(method set)包含了Set方法。因此,*Settable是满足Setter对FromStrings函数的类型参数的约束的。

而如果我们直接使用type T Setter,那么编译器将给出下面错误:

type checking failed for main
prog.go2:30:22: Settable does not satisfy Setter (missing method Set)

如果我们使用type T Setter并结合使用FromStrings(*Settable),那么程序运行会panic。

https://go2goplay.golang.org/p/YLe2d78aSz-

3. 性能影响

根据这份技术提案中关于泛型函数和泛型类型实现的说明,Go会使用基于接口的方法来编译泛型函数(generic function),这将优化编译时间,因为该函数仅会被编译一次。但是会有一些运行时代价。

对于每个类型参数集,泛型类型(generic type)可能会进行多次编译。这将延长编译时间,但是不会产生任何运行时代价。编译器还可以选择使用类似于接口类型的方法来实现泛型类型,使用专用方法访问依赖于类型参数的每个元素。

4. 小结

Go泛型方案的即将定型即好也不好。Go向来以简洁著称,增加加泛型,无论采用什么技术方案,都会增加Go的复杂性,提升其学习门槛,代码可读性也会下降。但在某些场合(比如实现container数据结构及对应算法库等),使用泛型却又能简化实现。

在这份提案中,Go核心团队也给出如下期望:

We expect that most packages will not define generic types or functions, but many packages are likely to use generic types or functions defined elsewhere

我们期望大多数软件包不会定义泛型类型或函数,但是许多软件包可能会使用在其他地方定义的泛型类型或函数。

并且提案提到了会在Go标准库中增加一些新包,已实现基于泛型的标准数据结构(slice、map、chan、math、list/ring等)、算法(sort、interator)等,gopher们只需调用这些包提供的API即可。

另外该提案的一大优点就是与Go1兼容,我们可能永远不会使用Go2这个版本号了。

go核心团队提供了可实践该方案语法的playground:https://go2goplay.golang.org/,大家可以一边研读技术提案,一边编写代码进行实验验证。


我的网课“Kubernetes实战:高可用集群搭建、配置、运维与应用”在慕课网上线了,感谢小伙伴们学习支持!

我爱发短信:企业级短信平台定制开发专家 https://tonybai.com/
smspush : 可部署在企业内部的定制化短信平台,三网覆盖,不惧大并发接入,可定制扩展; 短信内容你来定,不再受约束, 接口丰富,支持长短信,签名可选。

2020年4月8日,中国三大电信运营商联合发布《5G消息白皮书》,51短信平台也会全新升级到“51商用消息平台”,全面支持5G RCS消息。

著名云主机服务厂商DigitalOcean发布最新的主机计划,入门级Droplet配置升级为:1 core CPU、1G内存、25G高速SSD,价格5$/月。有使用DigitalOcean需求的朋友,可以打开这个链接地址:https://m.do.co/c/bff6eed92687 开启你的DO主机之路。

Gopher Daily(Gopher每日新闻)归档仓库 – https://github.com/bigwhite/gopherdaily

我的联系方式:

微博:https://weibo.com/bigwhite20xx
微信公众号:iamtonybai
博客:tonybai.com
github: https://github.com/bigwhite

微信赞赏:
img{512x368}

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats