Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ruby 3の型推論やってます

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

Ruby 3の型推論やってます

Ruby 3の型推論やってます

Avatar for Yusuke Endoh

Yusuke Endoh

April 17, 2020
Tweet

More Decks by Yusuke Endoh

Other Decks in Programming

Transcript

  1. 雑談:endless新作 • ruby masterに新文法提案して仮採択されました • Endless method definition [Feature #16746]

    • この資料でもさっそく使っていきます! • と思ったけど、脳負荷がありそうなので控えめに 2 def foo = 42 def foo 42 end = と同じ
  2. Ruby 3の型を1ページで Ruby 3は型記述言語・型推論・型検査を提供したい 3 def inc: (Integer) -> Integer

    ① 型記述言語 (RBS; ruby-signature) def inc(n) = n+1 コード ② 型推論 (ruby-type-profiler) ③ 型検査 (Steep, Sorbet, …) この発表では ②型推論を 話します
  3. Demo: ao.rb 6 class Vec @x : Float @y :

    Float @z : Float initialize : (Float, Float, Float) -> Float x : () -> Float x= : (Float) -> Float ... vadd : (Vec) -> Vec vsub : (Vec) -> Vec vcross : (Vec) -> Vec vdot : (Vec) -> Float vlength : () -> Float vnormalize : () -> Vec 3Dベクトルのクラス ベクトル演算たち 座標 attr_accessor
  4. Demo: ao.rb 7 class Scene @spheres : [Sphere, Sphere, Sphere]

    @plane : Plane initialize : () -> Plane ambient_occlusion : (Isect) -> Vec render : (Integer, Integer, Integer) -> Integer end class Sphere @center : Vec @radius : Float initialize : (Vec, Float) -> Float intersect : (Ray, Isect) -> (NilClass | Vec) end 3つの球 球は中心と半径
  5. Demo: ao.rb 8 class Ray @org : Vec @dir :

    Vec initialize : (Vec, Vec) -> Vec org : () -> Vec dir : () -> Vec end class Isect @t : Float @hit : FalseClass | TrueClass @pl : Vec @n : Vec initialize : () -> Vec 光線は起点と方向 交点の判定・計算 交わるか否か boolean相当
  6. 普通の型システムとの違い • 普通の型システムはメソッド単位で解析する • 仮引数の型はどうする? • 手書きする(TypeScript他) • 我々の目的には合わない •

    使われ方を見て決める(ML他) • 右のような例で難しい 12 class A def foo = 42 end class B def foo = "str" end def f(n) n.foo #=> 何? end
  7. diff-lcs解析結果( そこそこ いい感じの例) 16 class Diff::LCS::Change include Comparable @element :

    NilClass | T | any @position : Integer | any @action : :+ | :- | any self.valid_action? : (:! | :+ | :- | :< | :== | :> | any ) -> (FalseClass | TrueClass) action : () -> (String | any ) position : () -> (Integer | any ) element : () -> (NilClass | T | any ) initialize : (String | any , Integer | any , NilClass | T | any ) -> NilClass to_a : () -> ([String | any , Integer | any , NilClass | T | any ]) unchanged? : () -> (FalseClass | TrueClass | any ) end 列の中の要素 追加削除の位置 追加 or 削除 ※元コードはStringでしたがデモのためSymbolに書き換えた 誤推定っぽいのは薄くしてます
  8. diff-lcs解析結果(難しい例) • 引数に依存して返り値の型が変わるメソッド • diff(ary, ary, DiffCallbacks) ➔ Array[Array[Diff::LCS::Change]] •

    diff(ary, ary, SDiffCallbacks)➔ Array[Diff::LCS::ContextChange] • オーバーロードのRBSは手書きしてください 17 module Diff::LCS self.diff : (Array[T] | Diff::LCS, Array[T] | any, ?NilClass) -> (Array[Array[Diff::LCS::Change | NilClass | any ] | Diff::LCS::Change | Diff::LCS::ContextChange | NilClass | any ] | any ) end
  9. type-profiler解析結果(いい感じの例) 20 class TP::Type include TP::Utils::StructuralEquality self.any : () ->

    TP::Type::Any self.bool : () -> TP::Type::Union self.nil : () -> TP::Type::Instance self.optional : (TP::Type | TP::Type::Any | TP::Type::Array | … | any) -> (TP::Type | TP::Type::Any | TP::Type::Array | … | any) self.guess_literal_type : (any) -> (TP::Type::Any | TP::Type::Array | … | TP::Type::Symbol) … end
  10. type-profiler解析結果(難しい例) • 再帰構造がfalse positiveになる例 • ExecutionPoint (EP)は外側のEPへの参照を持つ 21 class TypeProfiler::ExecutionPoint

    … @outer : NilClass | TypeProfiler::ExecutionPoint | any # 一番外側のEPをたどるコード ep = EP.new(…) while ep.outer ep = ep.outer #=>「NilClass#outerを呼ぶかも」警告が出る end ep.pc #=>「NilClass#pcを呼ぶかも」警告が出る
  11. FAQ • X | any は any と同じでは? • おっしゃるとおり

    • でもRBSプロトタイプ生成には便利なので あえて潰さずに残している 23
  12. アジェンダ • 型プロファイラの設計と性質 • デモと考察 • ➔型プロファイラの実装や工夫 • 扱う型 •

    分岐、可変長引数やキーワード引数 • ダミー実行や診断機能 • 今後 24
  13. 扱う型:普通のやつ • 普通のインスタンス • NilClass、Integer、String、Objectなど • ユーザ定義クラス • 普通のクラスオブジェクト •

    クラスメソッド定義などで必要 • クラスのクラスや、特異クラスは扱わない • any型 • 未定義メソッド呼出、ダミー実行(後述)などで発生 • anyのメソッド呼び出し(any.foo())は無視する 25
  14. 扱う型:コンテナ周り • 具体型:Symbol、リテラル型 • :key などは具体値として扱う • 整数 42 などは同一メソッド内では具体値として扱う

    • コンテナ型:配列とハッシュ • [X, Y, *Z]: 先頭はX型、次はY型、残りはZ型の配列 • [Integer, String]: 整数と文字列のタプル(ary[0]は先頭) • [*Integer]: 長さ不明の整数シーケンス • (RBSより表現力が少し強いので、RBSにするとき情報が落ちる) • {X=>Y, Z=>W}: キーがX型なら値はY型…、なハッシュ • {:key1=>Integer, Symbol=>String} 26
  15. 扱う型:その他 • Proc型: ブロック、具体値として扱う • callやyieldをなるべく正確にトレースするため • Union型: 型の和集合の型 •

    (Integer | String) : 整数か文字列 • 工夫(というか制限) • ([*Integer] | [*String])は[*(Integer|String)]に潰す • 不正確だけど、組合せ爆発を避けて解析高速化を選んだ 27
  16. 可変長引数とキーワード引数 • なんかそれっぽく動く(実装は地味に大変だった) 29 def foo(a, *r, z) r end

    foo(1, 2, 3) foo(1, 2, "S", 3) def foo: (Int, *Int|Str, Int) -> Array[Int|Str] def foo(n:, s:) { N: n, S: s } end foo(n: 42, s: "str") def foo: (n: Int, s: Str) -> {:N=>Int, :S=>Str}
  17. 「ダミー実行」(2) • ダミー実行は良し悪し • ゴミ情報が波及することも 31 # ao.rb抜粋 class Vec

    # このメソッドは使われない def vadd(b) # b: any Vec.new( @x + b.x, # any @y + b.y, # any @z + b.z, # any ) end end class Vec @x: Float | any @y: Float | any @z: Float | any # 今はチートコードを追加(広義の型注釈?) if _ = false v = Vec.new(0.0,0.0,0.0) v.vadd(v) end vaddをanyで呼び出すので @xにanyが記録される 記録されない特殊な anyで解決できる?
  18. 解析の診断機能 • pで型をrevealできる • 「ここにどんな型が来ると思ってる?」とか調べる • 解析到達経路をバックトレース風に表示する機能 32 def foo(n)

    p n end foo(1) foo("str") # Revealed types # reveal.rb:2 #=> Integer | String # Classes class Object foo : (Integer | String) -> (Integer | String) end
  19. 困難に対して • 解析が遅い ➔ (精度を犠牲にしつつ)高速化してきた • テストが必要 ➔ (荒削りだけど)ダミー実行 •

    誤推定・誤検出 ➔ (限界はあるけど)改善中 • 解析の理解が難しい ➔ (簡単だけど)診断機能 • 開発体験が未知 ➔ ? 33
  20. アジェンダ • 型プロファイラの設計と性質 • デモと考察 • 型プロファイラの実装や工夫 • ➔今後 •

    型プロファイラによるプログラミング体験 • 開発進捗と今後の予定 34
  21. 型プロファイラの開発体験? • ずるくないpolyglot • 普通の実行に加え、型レベル実行を意識して書く • 別言語ではないのでそこまでつらくないと思う • それでもメリットはある(はず) •

    型注釈なしの記述は疑いなくシンプル • 型プロファイラが解析できる≒素直な良いコード? 37 def foo(n: Integer | String) : Integer | String p n end foo(1) foo("str") def foo(n) p n end foo(1) foo("str") vs.
  22. 進捗と今後 • 現状:やっとスタート地点 • 解析器の基本設計ができた • Rubyのおおよその言語機能がサポートできてきた • 組み込みクラスの知識をRBSから取り込んだ •

    今後:実験と改善を繰り返す • バグの洗い出しと修正 • プログラミング体験の設計と不足機能の実装 • 診断機能、差分更新機能 • Railsアプリ解析用のドライバ開発 • など 38
  23. 説明しなかったこと • オーバーロードの推定は諦めた • 爆発する • オーバーロードするときは基本的に手書きして • 再帰呼び出しはいい感じにできる •

    でも再帰的なデータ構造のハンドリングは微妙 • カスタムメソッド • 型プロファイラプラグイン • インスタンス変数の配列の破壊 • を説明するには、まずコンテナ型がメソッドを跨がらな いことを説明しないと…… 40