Ruby 非动态,也非静态,Ruby 3 Typing 的第三条路

kamiiyu · 2016年12月02日 · 最后由 Mark24 回复于 2020年09月25日 · 6442 次阅读
本帖已被设为精华帖!

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油管直通车

02.ruby3_typing

演讲者 Matz 就不多做介绍了。

01.matz

官方没有提供在线版的 PPT,我把这个日语演讲 “翻译” 成以下的文字,为了保持文字通顺,稍微加工了一下。


Ruby3 Typing(Matz)

在 2010 年以来的新语言很多都是静态类型语言(Static Typed Languages),例如 TypeScript、Flow、Go、Swift 等等。
与之相比,Ruby 没有静态类型,又是上世纪 90 年代语言,所以有些人会说 “Ruby is Dead”、“Rails is Dead”。

03.no_static_typing

但技术有时候就像钟摆,有时候偏向一种技术,有时候又偏向另外一种技术,这是常有的事情。例如静态类型与动态类型 “之争”。

  • 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有共同的superclassinterface,所以无法通过编译。

Duck typing 使我们在开发的时候不需花时间研究各个 Class 间的关系,大大降低开发者的开发成本,而且扩展起来也更加灵活。
所以我们可以认为,在 Ruby 的 type 就是 “Duck”。“Duck” 不是 Java 那样的Nominal type,更不是 Class,它是一种被期待的行为。

04.duck_is_behavior

“期待” 只是存在于我们的脑子里面,很暧昧的想法,也正因此 Ruby 的 Type 可以很灵活,对比用 Class 来定义 Type 的方式会有很多限制。

04.typing_by_class

对比 Nominal typing,我(Matz)更喜欢 Go 的 Interface,也就是Structural Subtyping这种方式。

04.go_interface

在上面的例子中,上面三行定义一个包含Write方法的 interface LogDst,下面三行的 log 函数接受两个参数LogDst和字符串mesg。在这个函数里我们只需要LogDstWrite的行为(方法)就可以了, 它可能是输出到 standard IO、String,或者其他什么地方,我们并不需要关心它的内部逻辑。
Structural Subtyping 和 Duck typing 同样保持很好的灵活性,当然我(Matz)还是更喜欢 Ruby 的 Duck Typing :-)。

05.duck_typing_is_awesome

DRY(Don't repeat yourself) 是 Ruby 另外一个重要原则。

06.do_not_repeat_yourself

为了避免不必要的重复,我们不会在程序写实际上不需要的东西,也就是说 Ruby 程序的运行不依赖于 type annotations,因此我们就不需要它们,甚至要去除它们。

但是 Dynamic Typing 也是存在不少不足的地方。

  • 在程序运行时能发现错误
    07.errors_only_found_in_runtime
  • error message 不友好,信息量少,下面可能是我们最熟悉但又 “莫名其妙” 的错误信息
    08.undefined_method
  • 如果测试覆盖不够,可能会有预想不到的 typing error
  • 缺少文档,像 Ruby 这样没有类型的语言写程序的时候非常爽,但读程序的时候就可能有困难了,所以有些人会写下面这样的注释
    09.document

可能会有人吐槽,最终不还是要把类型写出来吗。。。
但。。。无论如何还是不想指定类型,绝对不想。。。(Matz 特别强调两次,全场都笑了)

10.do_not_want_to_specify_types

因为这样会降低程序的灵活性,但为了以后的维护,我们又希望有可读性好的文档。
除了把类型信息像刚才那样写在注释外,还有另外一个做法是把他写在文档中。把类型写在文档里,但实际程序又不会做类型检查,到头来实际两边都没有讨好。
至少对 Ruby 来说 Type Annotation,Mixed/Gradual 都不是好主意。

正是因为还有以上种种问题,Ruby 还有很多改进的空间,

11.room_for_improvement

并且我们作为一个工程师应该要主动去解决这些问题。

12.solve_problems

有些人提出了 Static Typing with Type Inference的解决方案,但这个方案还是没能解决静态类型不够灵活的缺点。
又有人提出 Gradual Typing 或者 Optinal Typing 的解决方案,但这两种类型实际还是静态类型,因此灵活性这个问题还是没能得到解决。
Ruby 需要除上面以外其他的什么东西,一种像 Static Typing 这样进行类型检查,但又像 Duck Typing 这样灵活的类型。

13.static_type_with_duck_typing

暂且就把她叫做 Soft typing。

14.soft_typing

Soft typing 是一套用行为来定义的 Type System。

15.soft_typing_system

所谓行为就是一组的方法和参数数量、类型等。

16.behavior_is_a_set_of_method

回到刚才日志输出的例子,Go 版本的 interface 其实可以让程序自动生成,并且我们写程序的时候也不需要关心 interface(Type) 的名字 (取名字对有些人来说是件麻烦事),

17.not_to_worry_about_type_names

因此我们只可以忽略这些细节,专注于程序开发。(Happy Programming 的真谛)

18.vague_ideas

例如,我们可以把 Type 信息搜集起来,就像放到数据库中一样。然后,我看可以从这个数据库中获取 Type 的定义和 Type 行为(方法)。
我们也这些 Type 信息看做是一种表达式(expression),

19.retrieve_a_type_as_a_expression

例如,我们可以检查当把 A 表达式赋值给 B 表达式时是否兼容。

20.check_compatiblity

我们也可以检查某个类型有没有对应的方法。

21.check_method

这样的做法也许并不能做到 100% 的类型检查,但还是比之前一点都没用要好。

22.better_than_nothing

如果找不到对应的类型信息,由于本来就是 dynamic typing,那么我们就退回到 dynamic typing 就可以了。

23.fallback

有两种方式实现 Soft typing。

一个是利用 ad-hoc type 的信息。
例如,有 a 表达式(也有可能是变量),我们期望她有 gsub,slice,map 三个方法,如果找不到有对应的 class 满足这个条件的,那就抛出错误信息。

24.ad_doc_example

但对于在运行时不断动态添加或修改的方法,这种检查方式就无能为力了。

另外一个是在运行时搜集类型信息,

25.collect_from_runtime

特别是在测试的时候,

26.collect_from_test

一般 Libray 或者 Gem 都会进行测试,那么我们可以在测试的同时,建立类型数据库。
这样我们就可以在发布 Gem 的同时,以某种方式一起创建和发布与之对应的类型数据库。

27.build_type_database_from_gem

IDE 也可以利用这些 Type Database 的信息,让我们可以构造更加有效率、聪明的开发环境。
可惜的是在先阶段以上这些暂时都还只是构想,我们还不能用到。

28.still_mere_concept

所以让我们一起期待 Ruby3 吧!

29.part_of_ruby3

最后,我们(Ruby committees)有一个很重要的信息传递给大家,我们是非常重视开发者的。

30.we_care_about_you

我们不会对 Dynamic typing 的 “缺点 “视而不见,或者叫开发者多做测试就了事,而是希望努力的改善 Ruby,让开发者有更好的开发体验。

31.willing_improve_development_experience

关于 Ruby3 什么时候发布,目前还是不知道。。。

32.i_dont_know

从 committee management 的角度来看,开源软件一般没有所谓的 dead line,也没有很明确的 road map,至少对于 Ruby 这个项目来说没有。但如果什么都没有又很难开展工作,因此我们对 Ruby3 的开发指定了 3 个目标。

33_three_goals_of_ruby3

就像当年美国登月一样,也是先定了一个困难、远大的目标,然后大家一起为之努力,最后成功。
那 Ruby3 的三个目标什么时候才能实现呢?我(Matz)希望在下一次的日本奥运会的时候。。。

34.wait_for_years

虽然 Ruby3 还” 遥遥无期 “,但 Ruby 前进的脚步是不会停止的。

35.keep_moving_forward

36.i_promise

我们会一直不遗余力的帮助广大开发者在编程中找到乐趣--Happy Hacking!

37.happy_hacking

Rei 将本帖设为了精华贴 12月02日 12:10

谁来说说今年 Matz 用了这个 slide 多少次。。。

#2 楼 @gyorou Matz 一直一年就准备个一两套 slides...

#2 楼 @gyorou 完全同意, Ruby 發展速度比 Rust / Python / Go 也慢很慢很多。

到底有没有类型?没看出个所以然来,放出个 demo 来看看啊

Matz 没提 Elixir,这个才是最强的后生吧。

无类型是把双刃剑。 用的确实超级灵活。

#6 楼 @chenge Koichi 在这次 rubykaigi 的关于 ruby3 新线程模型 guild 的分享里面提到了 Elixir,而且他就是日文版 programming elixir 的译者,所以可以预见 ruby3 的 cuncurrency 应该会有 inspired by elixir 的内容

类似 erlang/elixir 里加类型的方式?

Rei Ruby 需要一个静态类型验证系统 中提及了此贴 03月16日 02:49

『那 Ruby3 的三个目标什么时候才能实现呢?我(Matz)希望在下一次的日本奥运会的时候。。。 』

2020 神奇的一年,新冠疫情全球蔓延。

日本奥运会取消了

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册