Go 还是需要泛型的
Go 语言之父早期提到过 less is more[1] 的哲学,可惜社区里有不少人被带偏了。
每次 Go 增加语法上的新特性、新功能,就会有原教旨主义者跳出来扔出一句 less is more(或者也可能是大道至简),扬长而去,留下风中凌乱的你。
即使到了官方已经确定要增加泛型功能的 2020 年,依然有人煞有介事地写文章说为什么 go doesn't need generics[2],作为理智的 Gopher,最好不要对别人的结论尽信,至少要看看其它语言社区怎么看待这件事情。
Java 社区是怎么理解泛型的必要性的呢?
简而言之,泛型使类型(类和接口)能够在定义类、接口和方法时成为参数。就像我们更熟悉的在方法声明中使用的形式参数一样,类型参数为你提供了一种用不同的输入重复使用相同代码的方法。不同的是,形式参数的输入是值,而类型参数的输入是类型。
与非泛型代码相比,使用泛型的代码有很多好处。
在编译时进行强类型检查。Java 编译器对泛型代码进行强类型检查,如果代码违反类型安全就会报错。编译时的错误比运行时的错误更易修复。
消除类型转换。下面这段代码片段在没有泛型时,需要类型转换:
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
用泛型重写,代码不再需要进行类型转换:
List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0); // no cast
程序员可以编写泛型算法 使用泛型可以实现在不同类型上都可以工作的泛型算法的同时,保证类型安全性。
Gopher 可能对“类型安全”不太熟悉,举个例子,我们可以用 gods 库来实现下面的数据结构。
package mainimport ("fmt"sll "github.com/emirpasic/gods/lists/singlylinkedlist"
)func main() {list := sll.New()list.Add("a") // ["a"]
}
我们的本意是实现一个 string 的单链表,但是通用的数据结构库没有办法阻止用户向该 list 内插入非 string 类型的值,比如用户可以这样:
list := sll.New()list.Add("a") // ["a"]list.Add(2) // ["a", 2]
这显然不是我们想要的结果。
可见泛型最常见的场景是在类型安全的前提下实现算法流程,对于 Go 来说,我们使用的数据结构和算法来源有两个地方:container 标准库、第三方数据结构库,如 gods[3] 。
和我们前面举的例子一样,标准库的通用 container 的大多接口也是接收空 interface{},或返回空 interface{}:
package mainimport ("container/list"
)func main() {l := list.New()l.PushBack(4)l.PushFront("bad value")
}
做不到类型安全的话,那么用户代码就可能在运行期间发生断言产生的 panic/error。除了容器的功能容易被破坏,类似下面的 bug 也挺容易出现的:
package mainimport "fmt"type mystring stringfunc main() {var a interface{} = "abc"var b interface{} = mystring("abc")fmt.Println(a == b)
}
社区的其它尝试
社区曾经有一些靠代码生成实现的泛型库,如genny[4],其本质是使用文本替换来实现多种类型的代码生成。
genny 使用也比较简单,比如 example 里的例子:
package queueimport "github.com/cheekybits/genny/generic"// NOTE: this is how easy it is to define a generic type
type Something generic.Type// SomethingQueue is a queue of Somethings.
type SomethingQueue struct {items []Something
}func NewSomethingQueue() *SomethingQueue {return &SomethingQueue{items: make([]Something, 0)}
}
func (q *SomethingQueue) Push(item Something) {q.items = append(q.items, item)
}
func (q *SomethingQueue) Pop() Something {item := q.items[0]q.items = q.items[1:]return item
}
cat source.go | genny gen "Something=string"
// This file was automatically generated by genny.
// Any changes will be lost if this file is regenerated.
// see https://github.com/cheekybits/gennypackage queue// StringQueue is a queue of Strings.
type StringQueue struct {items []string
}func NewStringQueue() *StringQueue {return &StringQueue{items: make([]string, 0)}
}
func (q *StringQueue) Push(item string) {q.items = append(q.items, item)
}
func (q *StringQueue) Pop() string {item := q.items[0]q.items = q.items[1:]return item
}
想实现多种类型的结构就在生成代码时传入多种类型就可以了。
这种做法和人们调侃 Go 泛型时使用的 gif[5] 本质上也没什么区别。
语言的原生支持能让我们省事,并且也能在实现上更加严谨。
在 《Rise and Fall of Software Recipes》一书中,有这么一个故事:
Among the recent projects failing because (or despite) of strong processes, Obamacare is a telling example. It involves 50 contractors, has cost fortunes, was delivered late and crippled with bugs. It was developed using a typical waterfall process, and if only because of that, the Agile community started howling, claiming that they would have made the project a success[Healthcare.gov failure].
And when an Agile project fails like Universal Credit in Great-Britain[UniversalCredit] [NAO2013], even when the full report states that the lack of detailed blueprint – typical of Agile methodologies – was one of the factors that caused the failure, common Agile wisdom says it is because it was not applied properly, or should I say, not Agile enough.
简而言之,就是敏捷大师们其实非常双标,他们给出的方法论也不一定靠谱,反正成功了就是大师方法得当,失败了就是我们执行不力没有学到精髓。正着说反着说都有道理。
再看看现在的 Go 社区,buzzwords 也很多,如果一个特性大师不想做,那就是 less is more。如果一个特性大师想做,那就是 orthogonal,非常客观。
对于不想迷信大师的 Gopher 来说,多听听批评意见没坏处:go is not good[6]。
[1]
less is more: https://en.wikipedia.org/wiki/Less_is_more
[2]
go doesn't need generics: https://dzone.com/articles/go-doesnt-need-generics
[3]
gods: https://github.com/emirpasic/gods
[4]
genny: https://github.com/cheekybits/genny
[5]
gif: https://twitter.com/yogthos/status/883058510275149826
[6]
go is not good: https://github.com/ksimka/go-is-not-good
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 ...
- C# 篇基础知识11——泛型和集合
.NET提供了一级功能强大的集合类,实现了多种不同类型的集合,可以根据实际用途选择恰当的集合类型. 除了数组 Array 类定义在System 命名空间中外,其他的集合类都定义在System.Coll ...
最新文章
- 太赞了!NumPy 手写所有主流 ML 模型,由普林斯顿博士后 David Bourgin打造的史上最强机器学习基石项目!...
- UIButton状态探索和自定义
- ubutu14.04无法使用sudo,也无法切换到root用户去解决问题怎么办?
- 大数据测试之hadoop命令大全 2
- 【题目分析】1059 Prime Factors (25 分)
- 【effective c++读书笔记】【第8章】定制new和delete(2)
- JAVA反射-面试题
- python实现面试程序
- MySQL自定义函数(四十六)
- Mysql的Root密码忘记,查看或修改的解决方法(图文介绍)
- 语音情感识别研究进展综述
- Java 添加、验证PDF 数字签名
- 写一篇meta分析要多少时间?如何写好一篇Meta分析,你需要这样做
- Spring常用注解含义
- EntityRef:expecting“;”
- Java为PDF文档加密
- 冰达ROS机器人使用-实现slam建模、自主导航、避障
- python使用tkinter库,封装操作excel为GUI程序
- 怎么把视频转成mp3音频?
- linux内核c1bcbc40,Linux内核驱动