RubyKaigi 2018 型の話のまとめ
RubyKaigi 2018 では型についてのセッションがいくつかあり、個人的になるほどと思いながら色々考える機会になりました。
ざっくりここでまとめます。
matz KeyNote 質疑応答
質問者: 最近、TypeScript など静的型付けが流行ってるのが個人的に腹が立っている。 でもJavaScriptよりはTypeScriptがいいと思っている。 Ruby に今後、静的型付けという概念が入りますか?
Matz: まずRubyは今後も静的型付けを入れる事はないです。 型があった方が便利なケースは確かにある。例えばコンパイル時に判定できるなど。 でも十分に賢い人間であれば大きくないソフトウェアであれば型を推測することができると思います。 人間にできるということは未来では優秀なコンピュータなら型宣言がなくても型を推測することができるようになるのでは? 2010年台、2020年台、今は型宣言がとても効果的なのかもしれない。 でも2030年、2040年には自動型推論が主流になって、型宣言が古めかしいものになるかもしれない。
トレンドに逆らってまで頑なに型宣言を入れるつもりがないモチベーションを知ることができた気がします。
どこかで matz さんが言ってたと思うのですが 「機械に合わせて面倒なことをしたくない。機械に面倒なことをさせたい。」みたいな話かなと。
※免責: 発言そのまま書き起こしではありません。 ニュアンスなど多々違います
Steep
https://github.com/soutaro/steep
https://speakerdeck.com/soutaro/ruby-programming-with-type-checking
Usage
以下 https://github.com/soutaro/steep の README より抜粋
class Person @name: String @contacts: Array<Email | Phone> def initialize: (name: String) -> any def name: -> String def contacts: -> Array<Email | Phone> def guess_country: -> (String | nil) end class Email @address: String def initialize: (address: String) -> any def address: -> String end class Phone @country: String @number: String def initialize: (country: String, number: String) -> any def country: -> String def number: -> String def self.countries: -> Hash<String, String> end
class Person # `@dynamic` annotation is to tell steep that # the `name` and `contacts` methods are defined without def syntax. # (Steep can skip checking if the methods are implemented.) # @dynamic name, contacts attr_reader :name attr_reader :contacts def initialize(name:) @name = name @contacts = [] end def guess_country() contacts.map do |contact| # With case expression, simple type-case is implemented. # `contact` has type of `Phone | Email` but in the `when` clause, contact has type of `Phone`. case contact when Phone contact.country end end.compact.first end end class Email # @dynamic address attr_reader :address def initialize(address:) @address = address end def ==(other) # `other` has type of `any`, which means type checking is skipped. # No type errors can be detected in this method. other.is_a?(self.class) && other.address == address end def hash self.class.hash ^ address.hash end end class Phone # @dynamic country, number def initialize(country:, number:) @country = country @number = number end def ==(other) # You cannot use `case` for type case because `other` has type of `any`, not a union type. # You have to explicitly declare the type of `other` in `if` expression. if other.is_a?(Phone) # @type var other: Phone other.country == country && other.number == number end end def hash<i></i> self.class.hash ^ country.hash ^ number.hash end end
attr_reader
など解釈されない定義は@dynamic
で指定して解決する
Type Check
実装が型定義から外れていないかチェックできる
$ steep check lib lib/phone.rb:46:0: MethodDefinitionMissing: module=::Phone, method=self.countries (class Phone)
Scaffolding
実装から型定義の枠組みを作成できる
$ steep scaffold lib/*.rb
Type Profiler
Endoh さんセッション
- matz 曰く static type check で annotation は別ファイルが希望
- Type DB
- 複数の Type Profiler を集めて推定する
- Type Profiler
- 厳密な type interface は無理
- 大分して静的な解析, 実行時解析
感想
前年も型についてはセッションがありましたが、より具体的な方向性が見えてきたように思います。 Ruby の楽しさを失わずに型と仲良くできる道、とても楽しそう。