Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.

型プロファイラ:抽象解釈に基づくRuby 3の静的解析

555 Aufrufe

Veröffentlicht am

2020-04-17 型システム祭りオンライン
https://opt.connpass.com/event/169724/

Veröffentlicht in: Ingenieurwesen
  • Als Erste(r) kommentieren

型プロファイラ:抽象解釈に基づくRuby 3の静的解析

  1. 1. 型プロファイラ: 抽象解釈に基づくRuby 3の静的解析 遠藤 侑介(@mametter) 2020/04/17 型システム祭り 1
  2. 2. Yusuke Endoh (@mametter) • クックパッドで Ruby の開発者やってます • フルタイムRubyコミッタ • 本日は完全なaway 2
  3. 3. Yusuke Endoh (@mametter) • クックパッドで Ruby の開発者やってます • フルタイムRubyコミッタ • 本日は完全なaway • TAPL翻訳しました!!! (マウンティング) 2
  4. 4. アジェンダ • 背景:Ruby 3の静的型解析の計画 • 型プロファイラ:Ruby 3の型推論器 • デモ • 今後の話(与太話) 3
  5. 5. アジェンダ • ➔背景:Ruby 3の静的型解析の計画 • Rubyとは • 静的型解析の問題設定とアプローチ • 型プロファイラ:Ruby 3の型推論器 • デモ • 今後の話(与太話) 4
  6. 6. Rubyとは • Webアプリの開発でよく使われている言語 • ユーザ:クックパッド、GitHub、AirBnBなど • 現行の欠点は… • 遅い • 並列実行できない • 静的検証がない 5
  7. 7. Rubyとは • Webアプリの開発でよく使われている言語 • ユーザ:クックパッド、GitHub、AirBnBなど • 現行の欠点は… • 遅い • 並列実行できない • 静的検証がない 5 ➔Ruby 3の目標は……
  8. 8. Rubyとは • Webアプリの開発でよく使われている言語 • ユーザ:クックパッド、GitHub、AirBnBなど • 現行の欠点は… • 遅い • 並列実行できない • 静的検証がない 5 ➔Ruby 3の目標は…… → JITで3倍速くする
  9. 9. Rubyとは • Webアプリの開発でよく使われている言語 • ユーザ:クックパッド、GitHub、AirBnBなど • 現行の欠点は… • 遅い • 並列実行できない • 静的検証がない 5 ➔Ruby 3の目標は…… → JITで3倍速くする → 並行並列機能を入れる
  10. 10. Rubyとは • Webアプリの開発でよく使われている言語 • ユーザ:クックパッド、GitHub、AirBnBなど • 現行の欠点は… • 遅い • 並列実行できない • 静的検証がない 5 ➔Ruby 3の目標は…… → JITで3倍速くする → 並行並列機能を入れる → 静的型解析を入れる
  11. 11. Ruby 3の静的型解析の問題設定 • まつもとゆきひろ(Ruby言語設計者)曰く 6
  12. 12. Ruby 3の静的型解析の問題設定 • まつもとゆきひろ(Ruby言語設計者)曰く "I Hate Type Annotation" 6
  13. 13. Ruby 3の静的型解析の問題設定 • 実行前にバグの可能性を見つけられることは価値 • ただし、Rubyの開発体験はなるべく維持したい 7
  14. 14. Ruby 3の静的型解析の問題設定 • 実行前にバグの可能性を見つけられることは価値 • ただし、Rubyの開発体験はなるべく維持したい • Rubyの開発体験:簡潔さと柔軟性 • 簡潔な記述性は人類にはとても重要 • 動的性や柔軟性は現実世界の運用に役立っている 7
  15. 15. Ruby 3の静的型解析の問題設定 • 実行前にバグの可能性を見つけられることは価値 • ただし、Rubyの開発体験はなるべく維持したい • Rubyの開発体験:簡潔さと柔軟性 • 簡潔な記述性は人類にはとても重要 • 動的性や柔軟性は現実世界の運用に役立っている ➔解析の健全性を妥協しバランスのよい点を探す 7
  16. 16. Ruby 3の静的型解析のアプローチ Ruby 3は型記述言語・型検査・型推論を提供したい 8 def inc(n) n + 1 end コード
  17. 17. Ruby 3の静的型解析のアプローチ Ruby 3は型記述言語・型検査・型推論を提供したい 8 def inc: (Integer) -> Integer ① 型記述言語 (RBS; ruby-signature) def inc(n) n + 1 end コード
  18. 18. Ruby 3の静的型解析のアプローチ Ruby 3は型記述言語・型検査・型推論を提供したい 8 def inc: (Integer) -> Integer ① 型記述言語 (RBS; ruby-signature) def inc(n) n + 1 end コード ② 型推論 (ruby-type-profiler)
  19. 19. Ruby 3の静的型解析のアプローチ Ruby 3は型記述言語・型検査・型推論を提供したい 8 def inc: (Integer) -> Integer ① 型記述言語 (RBS; ruby-signature) def inc(n) n + 1 end コード ② 型推論 (ruby-type-profiler) ③ 型検査 (Steep, Sorbet, …)
  20. 20. Ruby 3の静的型解析のアプローチ Ruby 3は型記述言語・型検査・型推論を提供したい 8 def inc: (Integer) -> Integer ① 型記述言語 (RBS; ruby-signature) def inc(n) n + 1 end コード ② 型推論 (ruby-type-profiler) ③ 型検査 (Steep, Sorbet, …) ①+③=大体TypeScript
  21. 21. Ruby 3の静的型解析のアプローチ Ruby 3は型記述言語・型検査・型推論を提供したい 8 def inc: (Integer) -> Integer ① 型記述言語 (RBS; ruby-signature) def inc(n) n + 1 end コード ② 型推論 (ruby-type-profiler) ③ 型検査 (Steep, Sorbet, …) ①+③=大体TypeScript ②=型注釈なし解析を 試みる無謀な挑戦
  22. 22. アジェンダ • Ruby 3の静的型解析の計画 • ➔型プロファイラ:Ruby 3の型推論器 • 解析アルゴリズムの概要 • 普通の型システムとの違い • デモ • 今後の話(与太話) 9
  23. 23. Ruby 3の型推論 • 型プロファイラ:型レベルのRubyインタプリタ • メソッドが受け取った型・返した型を集めて表示する • 型(のようなもの)を抽象値ドメインとする抽象解釈 10 def foo(n) n.to_s end foo(42)
  24. 24. Ruby 3の型推論 • 型プロファイラ:型レベルのRubyインタプリタ • メソッドが受け取った型・返した型を集めて表示する • 型(のようなもの)を抽象値ドメインとする抽象解釈 10 def foo(n) n.to_s end foo(42) Integer
  25. 25. Ruby 3の型推論 • 型プロファイラ:型レベルのRubyインタプリタ • メソッドが受け取った型・返した型を集めて表示する • 型(のようなもの)を抽象値ドメインとする抽象解釈 10 def foo(n) n.to_s end foo(42) Integer String
  26. 26. Ruby 3の型推論 • 型プロファイラ:型レベルのRubyインタプリタ • メソッドが受け取った型・返した型を集めて表示する • 型(のようなもの)を抽象値ドメインとする抽象解釈 10 def foo(n) n.to_s end foo(42) Integer String def foo: (Integer) -> String
  27. 27. 型プロファイラ:分岐の扱い • 分岐:両方の節を実行して、Union型で合流する • ループ:型を拡大しながら収束するまで繰り返す 11 if rand < 0.5 x = 42 #=> Integer else x = "str" #=> String end p(x) #=> Integer|String
  28. 28. 話題はたくさんあるけど略 原理的な話 • 関数呼び出し、再帰呼び出し • クロージャ • 型を変更する変数代入 • コンテナ型 • 型を変更するコンテナ破壊 • flow-sensitiveな解析と エスケープ解析の援用 • any型の扱い • コールスタックなし大域脱出 • 値レベルの言語機能 実用上の話 • 解析速度と精度のトレードオフ • 配列はタプルかつシーケンス • 解析結果の説明性 • 解析未到達コードの解析 • バイトコード解析の制限 • 山ほどあるRuby言語機能 • 複雑すぎるRubyの引数 • オブジェクト指向の扱い • 組み込みライブラリの型 • 型レベル言語機能プラグイン 12 ※まだ未実装のネタも含みます
  29. 29. 普通の型システムとの違い • 普通の型システムはメソッド単位で解析する • 仮引数の型はどうする? • 手書きする(TypeScript他) • 使われ方を見て決める(ML他) 13
  30. 30. 普通の型システムとの違い • 普通の型システムはメソッド単位で解析する • 仮引数の型はどうする? • 手書きする(TypeScript他) • 我々の目的には合わない • 使われ方を見て決める(ML他) 13
  31. 31. 普通の型システムとの違い • 普通の型システムはメソッド単位で解析する • 仮引数の型はどうする? • 手書きする(TypeScript他) • 我々の目的には合わない • 使われ方を見て決める(ML他) • 右のような例で難しい • 構造的部分型? 13 class A def foo = 42 end class B def foo = "str" end def f(n) n.foo #=> 何? end
  32. 32. 型プロファイラの主要な問題点 • 解析が遅い(速度と精度のトレードオフ) • 解析の起点(テスト)が必要 • 未使用関数は引数anyとしか言えない • 誤推定は生じる • 解析結果の理解が難しい • 開発体験が未知 • いろいろ工夫を考えながらやってます 14
  33. 33. アジェンダ • Ruby 3の静的型解析の計画 • 型プロファイラ:Ruby 3の型推論器 • ➔デモ • 3Dトレーサ • 最長共通列ライブラリ • 型プロファイラ自身を型プロファイル • 今後の話(与太話) 15
  34. 34. いい感じのデモ:ao.rb • 3Dレイトレーサ • 300行のトイプログラム • Syoyo Fujita, Hideki Miura作 • https://code.google.com/ archive/p/aobench/ • 若干改変あり 16
  35. 35. Demo: ao.rb 17 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
  36. 36. Demo: ao.rb 17 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ベクトルのクラス
  37. 37. Demo: ao.rb 17 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ベクトルのクラス 座標
  38. 38. Demo: ao.rb 17 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
  39. 39. Demo: ao.rb 17 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
  40. 40. Demo: ao.rb 18 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
  41. 41. Demo: ao.rb 18 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つの球
  42. 42. Demo: ao.rb 18 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つの球 球は中心と半径
  43. 43. Demo: ao.rb 19 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
  44. 44. Demo: ao.rb 19 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 光線は起点と方向
  45. 45. Demo: ao.rb 19 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 光線は起点と方向 交点の判定・計算
  46. 46. Demo: ao.rb 19 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相当
  47. 47. Demo: ao.rb • いい感じでは? 20
  48. 48. 事例:diff-lcs • 最長共通部分列ライブラリ • ダウンロード数4位の 大人気ライブラリ→ • 簡単なテスト↓を起点に解析 21 https://bestgems.org/ require_relative "diff-lcs/lib/diff/lcs" class T; end Diff::LCS.diff([T.new]+[T.new], [T.new]+[T.new]) {}
  49. 49. diff-lcs解析結果(そこそこいい感じの例) 22 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 誤推定っぽいのは薄くしてます
  50. 50. diff-lcs解析結果(そこそこいい感じの例) 22 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 列の中の要素 誤推定っぽいのは薄くしてます
  51. 51. diff-lcs解析結果(そこそこいい感じの例) 22 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 列の中の要素 追加削除の位置 誤推定っぽいのは薄くしてます
  52. 52. diff-lcs解析結果(そこそこいい感じの例) 22 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に書き換えた 誤推定っぽいのは薄くしてます
  53. 53. diff-lcs解析結果(難しい例) • 引数に依存して返り値の型が変わるメソッド • diff(ary, ary, DiffCallbacks) ➔ Array[Array[Diff::LCS::Change]] • diff(ary, ary, SDiffCallbacks)➔ Array[Diff::LCS::ContextChange] •オーバーロードのRBSは手書きしてください 23 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
  54. 54. diff-lcsで出た警告の例 • flow-sensitiveな解析が必要 ※実際にはもっといっぱい出てます 個別に原因究明して、修正や改善検討をする…… 24 if callbacks.respond_to?(:finished_a) and … … callbacks.finished_a(event) #=>「NilClass#finished_aを呼ぶかも」警告が出る … else
  55. 55. 事例:type-profiler • 型プロファイラのコード • Rubyで書かれている(5000行くらい)ので • 型プロファイルできる 25
  56. 56. type-profiler解析結果(いい感じの例) 26 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
  57. 57. type-profilerいろいろ問題点(抜粋) • 再帰データ構造の扱いが微妙 • 巨大なUnionが出てきてつらい • 継承関係を利用してまとめる? • 型のエイリアスをうまく作る? • Object#method経由の呼び出しが追えない • 作り込みが足らない ※実際にはもっといっぱい(略) 27 (TP::Type | TP::Type::Any | TP::Type::Array | … | any)
  58. 58. FAQ • X | any は any と同じでは? • おっしゃるとおり • でも敵術のプロトタイプ生成には便利なので あえて潰さずに残している 28
  59. 59. アジェンダ • Ruby 3の静的型解析の計画 • 型プロファイラ:Ruby 3の型推論器 • デモ • ➔今後の話(与太話) 29
  60. 60. polyglot • 2つ(以上)の言語で解釈可能なプログラム • 例:RubyとJavaScriptのpolyglot • コツ:片方の言語だけで解釈されるように頭を使う 30 if (0) print("Hello Ruby") [ここはRuby] else console.log("Hello JS") [ここはJS] // end
  61. 61. 型付き言語のプログラムはpolyglot • インタプリタが動的な意味で解釈できる • さらに、型システムが静的な意味で解釈できる 31
  62. 62. 型付き言語のプログラムはpolyglot • インタプリタが動的な意味で解釈できる • さらに、型システムが静的な意味で解釈できる • 型注釈はチート(polyglotの観点では) • 型システムだけに指示を与えられるずるい記述 31
  63. 63. 型プロファイラの開発体験? • ずるくないpolyglot • 普通の実行に加え、型レベル実行を意識して書く • 別言語ではないので、そこまで辛くはない(はず) 32
  64. 64. 型プロファイラの開発体験? • ずるくないpolyglot • 普通の実行に加え、型レベル実行を意識して書く • 別言語ではないので、そこまで辛くはない(はず) • それでもメリットはある(はず) • 型注釈なしの記述は疑いなくシンプル 32 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.
  65. 65. 型プロファイラの開発体験? • ずるくないpolyglot • 普通の実行に加え、型レベル実行を意識して書く • 別言語ではないので、そこまで辛くはない(はず) • それでもメリットはある(はず) • 型注釈なしの記述は疑いなくシンプル • 型プロファイラが解析できる≒素直な良いコード? 32 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.
  66. 66. 進捗と今後 • 現状:やっとスタート地点 • 解析器の基本設計ができた • Rubyのおおよその言語機能がサポートできてきた • 組み込みクラスの知識をRBSから取り込んだ • 今後:実験と改善を繰り返す • バグの洗い出しと修正 • プログラミング体験の設計と不足機能の実装 • 診断機能、差分更新機能 • Railsアプリ解析用のドライバ開発 • など 33
  67. 67. 謝辞 • 三浦さん(mrubyでの先行研究事例) • soutaroさん(RBS、その他議論) • uenoBさんとesumiiさん(アドバイス多数) • matz、akr、ko1などのRuby開発者たち • Sorbetの人たち(Stripe, Shopifyなど) • Jeff Foster(10年の既存研究、別方法の型推論) 34
  68. 68. まとめ • Ruby 3は型記述言語・型推論・型検査を 提供したい • 型推論担当の型プロファイラをやってます • (寛容な心で)手伝ってくれる人たのむ! • https://github.com/mame/ruby-type-profiler 35
  69. 69. 説明しなかったこと • オーバーロードの推定は諦めた • 爆発する • オーバーロードするときは基本的に手書きして • 再帰呼び出しはいい感じにできる • でも再帰的なデータ構造のハンドリングは微妙 • カスタムメソッド • 型プロファイラプラグイン • インスタンス変数の配列の破壊 • を説明するには、まずコンテナ型がメソッドを跨がらな いことを説明しないと…… 36

×