为什么 Go 的泛型一拖再拖?(转)
转载地址:https://mp.weixin.qq.com/s/ftmuA9g7QPAwSwiswRuSuw
前段时间 Go 语言的泛型讨论频频出现在各微信群,且又冲上了国内外各大文章的 “头条”:
信息汇总来看,Go 泛型这几年会出,但大体来讲现在 Go 泛型又又又推迟了。好家伙,我最早了解到时是考虑 Go1.16 释出,后面又推到了 Go1.17,接着现在又延期到了 Go1.18 了(2021 年底)。
看到了信息的表象后,再想想为什么泛型 “这件事情” 突然醒目起来了,其原因之一是由官方Go,11 岁[1] 的博文所引爆的。
同时近日举办的 GopherCon2020 大会,Robert Griesemer 分享的 Typing [Generic] Go。更正式的让 Go 泛型更面向了大众,也侧面的说明官方认为其已经到达了一个新的阶段了,进入最终实现阶段。
事不宜迟,既然官方都已经摩拳擦掌了,我们的学习之路也得跟上,因此本文将会介绍 Go 泛型现在的情况,并通过在介绍过程中不断思考最后得出一个为什么。
什么是泛型
泛型程序设计(generic programming)是程序设计语言的一种风格或范式。泛型允许程序员在强类型语言中编写代码时,使用一些以后才确定的类型,其在真正实例化时才会为这些参数指确定类型。
另外各语言和其编译器、运行环境对泛型的支持均不一样,因此需要针对来辩证。
简单来讲,泛型就是参数化多态。其可根据实参类型生成不同的版本,支持任意数量的调用:
func F(a, b T) T{ return a+b }// T 为 int
F(1, 2)// T 为 string
F("1", "2")
在编译时期编译器便确定其 T 的入参类型。这也是 Go 泛型实现的要求之一 “编译时类型安全”。
为什么需要泛型
这时候可能会有人说,没有泛型也可以啊...感觉写业务代码没什么影响,与其搞泛型不如搞好 errors(具体新消息可参见:重磅:Go errors 将不会有任何进一步的改进计划)。
但泛型是有其所需的场景,最常见的是像基础库在处理获取配置中心数据时,就要处理类型,时常遇到下述场景:
手写一个 “泛型” 如果使用接口(interface)类型来做,也得 switch.(type)
枚举出所有的基础类型。这显然并不合理,也没法做太复杂的逻辑,而且所支持的类型还泄露。
另外同时单从语言层面来讲,泛型支持是一个必然事件了,因为泛型的存在对解决特定领域的问题存在一定的意义。
接口和泛型有什么区别
在上面我们有提到接口(interface)类型,这时候就出现了泛型的第二个经典问题。那就是 “接口和泛型有什么区别?”,为什么不用接口来实现 “泛型”:
type T interface { ... }
func F(a, b T) T { return a+b }
也像这么一回事,但在这里存在一个致命的缺陷。那就是接口的入参和出参均可以在运行时表现为不同的类型:
F("煎鱼", 233)
要做好,还得依靠内部去对参数进行断言,否则作为 string 类型的煎鱼又如何和 int 类型的 233 相加呢,那是必然报错的。
而反过来看真 “泛型” 的实际使用,编译器会保证泛型函数的入参和出参必须为同一类型,有强制性的检验:
// 报错:type checking failed for main
F("煎鱼", 233)// 必须为同一类型,才能正常运行
F(666, 233)
两者存在本质上的区别,泛型会更安全,能够保证编译早期就发现错误,而不是等到运行时(并且可能会存在隐性的 BUG)。
总体来讲,泛型相较接口有如下优点:
更安全:编译早期就能发现错误。
性能好:静态类型。
过去:为什么那么久都没有泛型
前几段在社区的微信群看到一位小伙伴吐槽 “Go 语言居然没有泛型?”。
变相来看,可能其会认为 ”Go 都已经 11 岁了,2020 年了居然还没有泛型?”。
这显然是不对的,因为泛型本质上并不是绝对的必需品,更不是 Go 语言的早期目标,因此在过往的发展阶段没有过多重视这一点,而是把精力放在了其他 feature 上。
另外 Go 语言在以往其实进行过大量的泛型 proposal 试验,基本时间线(via @changkun)如下:
简述 | 时间 | 作者 |
---|---|---|
[Type Functions] | 2010年 | Ian Lance Taylor |
Generalized Types | 2011年 | Ian Lance Taylor |
Generalized Types v2 | 2013年 | Ian Lance Taylor |
Type Parameters | 2013年 | Ian Lance Taylor |
go:generate | 2014年 | Rob Pike |
First Class Types | 2015年 | Bryan C.Mills |
Contracts | 2018年 | Ian Lance Taylor, Robert Griesemer |
Contracts | 2019年 | Ian Lance Taylor, Robert Griesemer |
Redundancy in Contracts(2019)'s Design | 2019年 | Ian Lance Taylor, Robert Griesemer |
Constrained Type Parameters(2020, v1) | 2020年 | Ian Lance Taylor, Robert Griesemer |
Constrained Type Parameters(2020, v2) | 2020年 | Ian Lance Taylor, Robert Griesemer |
Constrained Type Parameters(2020, v3) | 2020年 | Ian Lance Taylor, Robert Griesemer |
虽然偶有中断,但仔细一看,2010 年就尝试过,现在 2020 年了,也是很励志了,显然官方也是在寻路和尝试的过程中,但一直没有找到相较好的方案,大多都存在问题,社区对方案的争议也不断。
现在:Go 泛型
泛型尝鲜的方式有两种方式。线上 Ian Lance Taylor 提供了一个在线编译的 go2go[2]:
image
另外一种是线下,也就在本地安装 Go 的特定分支版本:
$ git clone https://github.com/golang/go
$ git checkout dev.go2go
$ cd src && ./all.bash
不过这种本地安装的方法会耗时比较久,初步尝试的话建议使用 go2go 就可以了。
而在尝鲜时,可以看到在代码块中声明了一个 Print
方法,其函数签名主体分为三部分:
函数签名
咋一看,变量 T 的这个关键字 any
是什么?早期泛型你可能有听说合约(Contract),难道这就是合约。其实严格意义上来讲并不是,因为为了更一步简化语法,合约在 2020.06.07 已经正式移除。
其已改头换面,现在只需要写参数化的 interface。而上述的 any
关键字是一个预定义的类型约束,声明后将允许任何类型用作类型实参,并且允许函数使用用于任何类型的操作。
从语法分析的角度来讲,Print
方法一共包含了如下属性(从左到右):
type list:声明了入参的类型列表为一个
T
变量,其可以传任意类型的参数。parameter list:声明了入参的参数列表为
T
变量的切片,且形参为s
。return type list:声明了函数的返回参数列表。
上述函数签名便是一个 Go 泛型的基本样子,由于本文并不是 CRUD 泛型,便不展开案例(且现有泛型还不成熟)。
若大家有兴趣,强烈建议阅读提案:Type Parameters - Draft Design[3]。
泛型的战争
为什么不用尖括号
在社区中很多同学在讨论的一个问题,那就是 “为什么 Go 泛型不像 C++ 和 Java 那样使用尖括号?,也出现了 “Go 一直标榜业界工程实践类的榜样,为什么就是不用尖括号” 的言论?
思考问题我们不只看表面,官方说不行,那么我们可以倒推来看,看看 Go 语言就用尖括号:
func print<type T>(list []T) {print<int>(numbers)
print<string>(strings)
print<float64>(floats)
普通的函数声明看上去似乎结构清晰,没有什么大问题的。接着往下看:
a := w < x
b := y > (z)
我们继续把代码演进一下,简洁一点:
a, b := w < x, y > (z)
这时候就犯难了,不仅编译器难以解析,人也很难判别,到底指的是:
a := w < x
b := y > (z)
又或是:
a, b := w<x, y>(z)
从上述代码来看,使用尖括号难以分别,因为没有类型信息,就无法确定赋值的右侧是一对表达式 w < x和y > (z)
,还是返回两个结果值 w<x, y>(z)
的泛型函数实例化和调用,其存在歧义。
要解决还要引入新的约束,会破坏 Go1 的兼容性承诺,这显然是不合理的。
为什么不用括号
其实最早 Go 泛型的版本是使用了括号的模式,虽然能用,但是用括号会引入新的解析歧义。例如:
var f func(x(T))
从语法上来讲,你无法识别他是未命名参数的 x(T)
函数,还是类型名为参数的 (T)
函数。
同时 Go 语言还存在强制类型转换这一语法,假设代码是 []T(v1)
和 []T(v2){}
,那么你在开括号处,就无法得知其是否代表类型转换。
甚至在函数的完整声明上,我们都会感到困惑:
func F(T any)(v T)(r1, r2 T)
函数入参、泛型、返回值声明均都是括号,造成了语义不清,这显然也是不合理的。
为什么不用书名号(«»)
想的美,并不想使用非 ASCII,未来更没打算支持。
总结
在本文中我们从多个维度介绍了 Go 泛型的相关内容,既了解到了上段时间 Go 泛型再度火爆的信息来源是什么。也知道了 Go 泛型是什么,与接口的区别。
同时我们还针对业界常见的一些疑问,例如接口和泛型的区别,泛型的历史,泛型的尖括号/括号/书名号之争进行了解释和说明。
最后我们回答一下最开始的疑问,”为什么 Go 的泛型一拖再拖“,主要如下:
Go 语言的早期目标(工作重点)并不是泛型。
Go 语言在 2010-2020 年都有间断在做 Go 泛型的 proposal,但总是 ”失败“,在不断地吸收经验。
Go 语言社区的意见反馈是真的多,单用什么符号表示泛型,不想要泛型都争论不休。
Go 语言的泛型现在还不成熟,很多细节其实并没有支持好。
很显然,在保证 Go1 向后兼容性的同时,Go 官方也不想直接妥协出一个随便的方案,因此总是不断地在改进。
随着 Go 语言的在业内的不断应用,泛型也和 errors 一样被推上风头浪尖。
为什么 Go 的泛型一拖再拖?(转)相关推荐
- 【C#】集合_哈希表_字典_泛型_文件
数组能做到:存放同种类型数据,且数据个数确定 object类型的数组能满足:放各种类型的数据,确定放多少个,但是随意插入元素,数组做不到 集合能做到:存放各种数据类型,且不确定存放多少个,能做到随意插 ...
- 2021年大数据常用语言Scala(三十六):scala高级用法 泛型
目录 泛型 定义一个泛型方法 定义一个泛型类 上下界 协变.逆变.非变 非变 协变 逆变 泛型 scala和Java一样,类和特质.方法都可以支持泛型.我们在学习集合的时候,一般都会涉及到泛型. sc ...
- 对比两个同类型的泛型集合并返回差异泛型集合 ——两个List类名的比较
1: /// <summary> 2: /// 对比两个同类型的泛型集合并返回差异泛型集合 3: /// </summary> 4: /// <typeparam nam ...
- 利用委托和泛型实现树的常用操作
在日常开发中,经常遇到对树的操作,我们可以利用泛型和委托对这些树进行操作,这样就不需要每有一个树就要实现相应的功能了. 源码在http://files.cnblogs.com/haiconc/Lang ...
- java 泛型 父子,Java泛型-mb601cf8a78cc07的博客-51CTO博客
Java泛型 泛型类 即把不确定的数据元素类型用一个泛型占位符表示@Data public class Person { private T name; private T address; }Per ...
- java 泛型 .net_Java基础11:Java泛型详解
本文对java的泛型的概念和使用做了详尽的介绍. 本文参考https://blog.csdn.net/s10461/article/details/53941091 具体代码在我的GitHub中可以找 ...
- java 获取泛型的type,如何获取泛型的Type类型
开发中很多时候都遇到或使用到泛型.例如在json转换成bean对象或其他对象,而对象中存在泛型,这时候需要用到TypeToken. Type:是java里的 java.lang.reflect.Typ ...
- Java中创建泛型数组
Java中创建泛型数组 使用泛型时,我想很多人肯定尝试过如下的代码,去创建一个泛型数组 T[] array = new T[]; 当我们写出这样的代码时编译器会报Cannot create a gen ...
- 第八章 泛型程序设计
1.带有[超类型限定 super]的通配符可以向泛型对象写入,带有[子类型限定 extends]的通配符可以从泛型对象读取,反之则不然. 转载于:https://www.cnblogs.com/bao ...
最新文章
- 它来了!ROS2从入门到精通:理论与实战
- PHP 判断数据类型
- 小蚂蚁学习数据结构(32)——二叉排序树的概念
- gcc 的visibility
- c语言常量结构体的成员,c语言之结构体
- WPF 密码框水印与明文切换
- 单选框 RadioButton 1130
- 在家远程办公效率低?那你一定要收好这个「在家办公」神器!
- 多种方式创建 Entity Framework Core 上下文
- matlab中 编程如和隐藏,在matlab中编程(如何实时处理)
- 关于数据准备时,自动棌番的主键,这一字段数据的注意(IT总结之五)
- 主从复制之操作实践(二)
- c语言上机作业题及答案,C语言上机题库及答案
- 脑皮质算法(2)一种基于新皮层网格细胞的智能和皮质功能的框架
- 电脑连接不上wifi,怎么办?
- 硬盘格式化后数据还可以恢复吗?格式化硬盘的恢复方法
- UE4蓝图API翻译【节点】--- Get All Actors with Tag
- 基于MATLAB的指纹对比识别系统
- 微信公众号刷票思路 Python
- 用计算机弹九八k谱子,拼音输出法(计算机).ppt
热门文章
- 运动会管理系统论文java_java+ssh+mysql大学运动会管理系统(源码+论文+任务书+ppt)...
- C# Tailor Your Application by Building a Custom Forms Designer with .NET
- java+connect+time+out_聊聊jdk httpclient的connect timeout异常
- vue点击input框出现弹窗_vue组件实现弹出框点击显示隐藏效果
- easyplayerpro 使用说明_EasyPlayerPro(Windows)流媒体播放器开发之ffmpeg log输出报错
- 通过系统进程查找sql语句
- 【Processing日常2】群星1
- go语言学习初探(二)基础语法
- iOS--百度地图相关功能的实现
- 数据库事务隔离级别+Spring 声明性事务隔离级别