Matz 在 2016 年的 Rubykaigi 里做了一个关于 Ruby3 Typing 的分享。
首先简单介绍一下什么是RubyKaigi。
Kaigi 就是日语【会議】的罗马字母写法,顾名思义也就是在日本举行的 RubyConf。
RubyKaigi 在 2006 年首次举行时的名字就是【日本 Ruby カンファレンス】(Japan Ruby Conference),由于容易与Ruby Central混淆,因此在 2007 年改名为【日本 Ruby 会議】,直到 2011 年停办。
2013 年大会重开,改名为【RubyKaigi】,并统一使用英语作为大会的官方名称。
今年的RubyKaigi2016在 9 月 8 号到 10 号京都举行。由于近水流台,有不少像 Matz 这样的 Ruby committer 参与分享,所以大会含金量非常高。
官网中以及发布了所有视频,希望以后有机会可以亲身去感受下。
大会的首个 Topic 是 Matz 的 Ruby3 Typing(油管直通车)
演讲者 Matz 就不多做介绍了。
官方没有提供在线版的 PPT,我把这个日语演讲“翻译”成以下的文字,为了保持文字通顺,稍微加工了一下。
在 2010 年以来的新语言很多都是静态类型语言(Static Typed Languages),例如 TypeScript、Flow、Go、Swift 等等。
与之相比,Ruby 没有静态类型,又是上世纪 90 年代语言,所以有些人会说“Ruby is Dead”、“Rails is Dead”。
但技术有时候就像钟摆,有时候偏向一种技术,有时候又偏向另外一种技术,这是常有的事情。例如静态类型与动态类型“之争”。
1970s 到 1980s 左右,从 Smalltalk、lisp 摆到 Java、C++
接下来又从 Java 摆到 Ruby、JavaScript
到最近又从 Ruby、JavaScript 摆到 Swift、Go
那么未来 Swift、Go 又会摆到哪里去呢?未来 Ruby3 的类型又会有什么改变呢?
首先我们需要知道什么是 Ruby 的 type?对于动态类型语言来说,Class 不是类型。
另外在 Ruby 其中一个重要的原则就是Duck typing,也就是说对于一个 Ruby 对象来说,我们不关心她继承关系(inheritance),也不关心她的内部结构(structure),我们只关心她的行为(behaves)。
请看下面String IO
的例子。
Ruby 版本的日志输出代码片段:
log(STDERR, "error!")
静态类型版本:
log(dst IO, mesg String)
Ruby StringIO
版本:
sio = StringIO.new()
log(sio, "error!")
sio.string # => retrieve string
上面的StringIO
例子在静态类型的世界行不通,因为StringIO
没有与IO
有共同的superclass
或interface
,所以无法通过编译。
Duck typing 使我们在开发的时候不需花时间研究各个 Class 间的关系,大大降低开发者的开发成本,而且扩展起来也更加灵活。
所以我们可以认为,在 Ruby 的 type 就是“Duck”。“Duck”不是 Java 那样的Nominal type,更不是 Class,它是一种被期待的行为。
“期待”只是存在于我们的脑子里面,很暧昧的想法,也正因此 Ruby 的 Type 可以很灵活,对比用 Class 来定义 Type 的方式会有很多限制。
对比 Nominal typing,我(Matz)更喜欢 Go 的 Interface,也就是Structural Subtyping这种方式。
在上面的例子中,上面三行定义一个包含Write
方法的 interface LogDst
,下面三行的 log 函数接受两个参数LogDst
和字符串mesg
。在这个函数里我们只需要LogDst
有Write
的行为(方法)就可以了,它可能是输出到 standard IO、String,或者其他什么地方,我们并不需要关心它的内部逻辑。
Structural Subtyping 和 Duck typing 同样保持很好的灵活性,当然我(Matz)还是更喜欢 Ruby 的 Duck Typing :-)。
DRY(Don't repeat yourself) 是 Ruby 另外一个重要原则。
为了避免不必要的重复,我们不会在程序写实际上不需要的东西,也就是说 Ruby 程序的运行不依赖于 type annotations,因此我们就不需要它们,甚至要去除它们。
但是 Dynamic Typing 也是存在不少不足的地方。
可能会有人吐槽,最终不还是要把类型写出来吗。。。
但。。。无论如何还是不想指定类型,绝对不想。。。(Matz 特别强调两次,全场都笑了)
因为这样会降低程序的灵活性,但为了以后的维护,我们又希望有可读性好的文档。
除了把类型信息像刚才那样写在注释外,还有另外一个做法是把他写在文档中。把类型写在文档里,但实际程序又不会做类型检查,到头来实际两边都没有讨好。
至少对 Ruby 来说 Type Annotation,Mixed/Gradual 都不是好主意。
正是因为还有以上种种问题,Ruby 还有很多改进的空间,
并且我们作为一个工程师应该要主动去解决这些问题。
有些人提出了 Static Typing with Type Inference的解决方案,但这个方案还是没能解决静态类型不够灵活的缺点。
又有人提出 Gradual Typing 或者 Optinal Typing 的解决方案,但这两种类型实际还是静态类型,因此灵活性这个问题还是没能得到解决。
Ruby 需要除上面以外其他的什么东西,一种像 Static Typing 这样进行类型检查,但又像 Duck Typing 这样灵活的类型。
暂且就把她叫做 Soft typing。
Soft typing 是一套用行为来定义的 Type System。
所谓行为就是一组的方法和参数数量、类型等。
回到刚才日志输出的例子,Go 版本的 interface 其实可以让程序自动生成,并且我们写程序的时候也不需要关心 interface(Type) 的名字 (取名字对有些人来说是件麻烦事),
因此我们只可以忽略这些细节,专注于程序开发。(Happy Programming 的真谛)
例如,我们可以把 Type 信息搜集起来,就像放到数据库中一样。然后,我看可以从这个数据库中获取 Type 的定义和 Type 行为(方法)。
我们也这些 Type 信息看做是一种表达式(expression),
例如,我们可以检查当把 A 表达式赋值给 B 表达式时是否兼容。
我们也可以检查某个类型有没有对应的方法。
这样的做法也许并不能做到 100% 的类型检查,但还是比之前一点都没用要好。
如果找不到对应的类型信息,由于本来就是 dynamic typing,那么我们就退回到 dynamic typing 就可以了。
有两种方式实现 Soft typing。
一个是利用 ad-hoc type 的信息。
例如,有 a 表达式(也有可能是变量),我们期望她有 gsub,slice,map 三个方法,如果找不到有对应的 class 满足这个条件的,那就抛出错误信息。
但对于在运行时不断动态添加或修改的方法,这种检查方式就无能为力了。
另外一个是在运行时搜集类型信息,
特别是在测试的时候,
一般 Libray 或者 Gem 都会进行测试,那么我们可以在测试的同时,建立类型数据库。
这样我们就可以在发布 Gem 的同时,以某种方式一起创建和发布与之对应的类型数据库。
IDE 也可以利用这些 Type Database 的信息,让我们可以构造更加有效率、聪明的开发环境。
可惜的是在先阶段以上这些暂时都还只是构想,我们还不能用到。
所以让我们一起期待 Ruby3 吧!
最后,我们(Ruby committees)有一个很重要的信息传递给大家,我们是非常重视开发者的。
我们不会对 Dynamic typing 的“缺点“视而不见,或者叫开发者多做测试就了事,而是希望努力的改善 Ruby,让开发者有更好的开发体验。
关于 Ruby3 什么时候发布,目前还是不知道。。。
从 committee management 的角度来看,开源软件一般没有所谓的 dead line,也没有很明确的 road map,至少对于 Ruby 这个项目来说没有。但如果什么都没有又很难开展工作,因此我们对 Ruby3 的开发指定了 3 个目标。
就像当年美国登月一样,也是先定了一个困难、远大的目标,然后大家一起为之努力,最后成功。
那 Ruby3 的三个目标什么时候才能实现呢?我(Matz)希望在下一次的日本奥运会的时候。。。
虽然 Ruby3 还”遥遥无期“,但 Ruby 前进的脚步是不会停止的。
我们会一直不遗余力的帮助广大开发者在编程中找到乐趣--Happy Hacking!