Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
型プロファイラ:抽象解釈に基づくRuby 3の静的解析
Search
Yusuke Endoh
April 20, 2020
Programming
0
26
型プロファイラ:抽象解釈に基づくRuby 3の静的解析
2020-04-17 型システム祭りオンライン
https://opt.connpass.com/event/169724/
Yusuke Endoh
April 20, 2020
Tweet
Share
More Decks by Yusuke Endoh
See All by Yusuke Endoh
An Invitation to TRICK: How to write weird Ruby programs
mame
1
700
TypeProf進捗
mame
0
19
12年前の『型システム入門』翻訳の思い出話
mame
14
1.9k
Good first issues of TypeProf
mame
4
6.4k
Revisiting TypeProf - IDE support as a primary feature
mame
1
2.3k
error_highlight: User-friendly Error Diagnostics
mame
0
21
TRICK 2022 Results
mame
0
24
クックパッド春の超絶技巧パンまつり 超絶技巧プログラミング編 資料
mame
0
29
Enjoy Ruby Programming in IDE and TypeProf
mame
0
28
Other Decks in Programming
See All in Programming
[JAWS-UG横浜 #76] イケてるアップデートを宇宙いち早く紹介するよ!
maroon1st
0
460
Scalaから始めるOpenFeature入門 / Scalaわいわい勉強会 #4
arthur1
1
310
Symfony Mapper Component
soyuka
2
730
MCP with Cloudflare Workers
yusukebe
2
220
生成AIでGitHubソースコード取得して仕様書を作成
shukob
0
340
StarlingMonkeyを触ってみた話 - 2024冬
syumai
3
270
선언형 UI에서의 상태관리
l2hyunwoo
0
150
開発者とQAの越境で自動テストが増える開発プロセスを実現する
92thunder
1
180
テストケースの名前はどうつけるべきか?
orgachem
PRO
0
130
テスト自動化失敗から再挑戦しチームにオーナーシップを委譲した話/STAC2024 macho
ma_cho29
1
1.3k
Refactor your code - refactor yourself
xosofox
1
260
コンテナをたくさん詰め込んだシステムとランタイムの変化
makihiro
1
120
Featured
See All Featured
Building Applications with DynamoDB
mza
91
6.1k
Typedesign – Prime Four
hannesfritz
40
2.4k
A designer walks into a library…
pauljervisheath
204
24k
Dealing with People You Can't Stand - Big Design 2015
cassininazir
365
25k
Producing Creativity
orderedlist
PRO
341
39k
4 Signs Your Business is Dying
shpigford
181
21k
StorybookのUI Testing Handbookを読んだ
zakiyama
27
5.3k
The World Runs on Bad Software
bkeepers
PRO
65
11k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
330
21k
Side Projects
sachag
452
42k
Fontdeck: Realign not Redesign
paulrobertlloyd
82
5.3k
KATA
mclloyd
29
14k
Transcript
型プロファイラ: 抽象解釈に基づくRuby 3の静的解析 遠藤 侑介(@mametter) 2020/04/17 型システム祭り 1
Yusuke Endoh (@mametter) • クックパッドで Ruby の開発者やってます • フルタイムRubyコミッタ •
本日は完全なaway 2
Yusuke Endoh (@mametter) • クックパッドで Ruby の開発者やってます • フルタイムRubyコミッタ •
本日は完全なaway • TAPL翻訳しました!!! (マウンティング) 2
アジェンダ • 背景:Ruby 3の静的型解析の計画 • 型プロファイラ:Ruby 3の型推論器 • デモ •
今後の話(与太話) 3
アジェンダ • ➔背景:Ruby 3の静的型解析の計画 • Rubyとは • 静的型解析の問題設定とアプローチ • 型プロファイラ:Ruby
3の型推論器 • デモ • 今後の話(与太話) 4
Rubyとは • Webアプリの開発でよく使われている言語 • ユーザ:クックパッド、GitHub、AirBnBなど • 現行の欠点は… • 遅い •
並列実行できない • 静的検証がない 5
Rubyとは • Webアプリの開発でよく使われている言語 • ユーザ:クックパッド、GitHub、AirBnBなど • 現行の欠点は… • 遅い •
並列実行できない • 静的検証がない 5 ➔Ruby 3の目標は……
Rubyとは • Webアプリの開発でよく使われている言語 • ユーザ:クックパッド、GitHub、AirBnBなど • 現行の欠点は… • 遅い •
並列実行できない • 静的検証がない 5 ➔Ruby 3の目標は…… → JITで3倍速くする
Rubyとは • Webアプリの開発でよく使われている言語 • ユーザ:クックパッド、GitHub、AirBnBなど • 現行の欠点は… • 遅い •
並列実行できない • 静的検証がない 5 ➔Ruby 3の目標は…… → JITで3倍速くする → 並行並列機能を入れる
Rubyとは • Webアプリの開発でよく使われている言語 • ユーザ:クックパッド、GitHub、AirBnBなど • 現行の欠点は… • 遅い •
並列実行できない • 静的検証がない 5 ➔Ruby 3の目標は…… → JITで3倍速くする → 並行並列機能を入れる → 静的型解析を入れる
Ruby 3の静的型解析の問題設定 • まつもとゆきひろ(Ruby言語設計者)曰く 6
Ruby 3の静的型解析の問題設定 • まつもとゆきひろ(Ruby言語設計者)曰く "I Hate Type Annotation" 6
Ruby 3の静的型解析の問題設定 • 実行前にバグの可能性を見つけられることは価値 • ただし、Rubyの開発体験はなるべく維持したい 7
Ruby 3の静的型解析の問題設定 • 実行前にバグの可能性を見つけられることは価値 • ただし、Rubyの開発体験はなるべく維持したい • Rubyの開発体験:簡潔さと柔軟性 • 簡潔な記述性は人類にはとても重要
• 動的性や柔軟性は現実世界の運用に役立っている 7
Ruby 3の静的型解析の問題設定 • 実行前にバグの可能性を見つけられることは価値 • ただし、Rubyの開発体験はなるべく維持したい • Rubyの開発体験:簡潔さと柔軟性 • 簡潔な記述性は人類にはとても重要
• 動的性や柔軟性は現実世界の運用に役立っている ➔解析の健全性を妥協しバランスのよい点を探す 7
Ruby 3の静的型解析のアプローチ Ruby 3は型記述言語・型検査・型推論を提供したい 8 def inc(n) n + 1
end コード
Ruby 3の静的型解析のアプローチ Ruby 3は型記述言語・型検査・型推論を提供したい 8 def inc: (Integer) -> Integer
① 型記述言語 (RBS; ruby-signature) def inc(n) n + 1 end コード
Ruby 3の静的型解析のアプローチ Ruby 3は型記述言語・型検査・型推論を提供したい 8 def inc: (Integer) -> Integer
① 型記述言語 (RBS; ruby-signature) def inc(n) n + 1 end コード ② 型推論 (ruby-type-profiler)
Ruby 3の静的型解析のアプローチ Ruby 3は型記述言語・型検査・型推論を提供したい 8 def inc: (Integer) -> Integer
① 型記述言語 (RBS; ruby-signature) def inc(n) n + 1 end コード ② 型推論 (ruby-type-profiler) ③ 型検査 (Steep, Sorbet, …)
Ruby 3の静的型解析のアプローチ Ruby 3は型記述言語・型検査・型推論を提供したい 8 def inc: (Integer) -> Integer
① 型記述言語 (RBS; ruby-signature) def inc(n) n + 1 end コード ② 型推論 (ruby-type-profiler) ③ 型検査 (Steep, Sorbet, …) ①+③=大体TypeScript
Ruby 3の静的型解析のアプローチ Ruby 3は型記述言語・型検査・型推論を提供したい 8 def inc: (Integer) -> Integer
① 型記述言語 (RBS; ruby-signature) def inc(n) n + 1 end コード ② 型推論 (ruby-type-profiler) ③ 型検査 (Steep, Sorbet, …) ①+③=大体TypeScript ②=型注釈なし解析を 試みる無謀な挑戦
アジェンダ • Ruby 3の静的型解析の計画 • ➔型プロファイラ:Ruby 3の型推論器 • 解析アルゴリズムの概要 •
普通の型システムとの違い • デモ • 今後の話(与太話) 9
Ruby 3の型推論 • 型プロファイラ:型レベルのRubyインタプリタ • メソッドが受け取った型・返した型を集めて表示する • 型(のようなもの)を抽象値ドメインとする抽象解釈 10 def
foo(n) n.to_s end foo(42)
Ruby 3の型推論 • 型プロファイラ:型レベルのRubyインタプリタ • メソッドが受け取った型・返した型を集めて表示する • 型(のようなもの)を抽象値ドメインとする抽象解釈 10 def
foo(n) n.to_s end foo(42) Integer
Ruby 3の型推論 • 型プロファイラ:型レベルのRubyインタプリタ • メソッドが受け取った型・返した型を集めて表示する • 型(のようなもの)を抽象値ドメインとする抽象解釈 10 def
foo(n) n.to_s end foo(42) Integer String
Ruby 3の型推論 • 型プロファイラ:型レベルのRubyインタプリタ • メソッドが受け取った型・返した型を集めて表示する • 型(のようなもの)を抽象値ドメインとする抽象解釈 10 def
foo(n) n.to_s end foo(42) Integer String def foo: (Integer) -> String
型プロファイラ:分岐の扱い • 分岐:両方の節を実行して、Union型で合流する • ループ:型を拡大しながら収束するまで繰り返す 11 if rand < 0.5
x = 42 #=> Integer else x = "str" #=> String end p(x) #=> Integer|String
話題はたくさんあるけど略 原理的な話 • 関数呼び出し、再帰呼び出し • クロージャ • 型を変更する変数代入 • コンテナ型
• 型を変更するコンテナ破壊 • flow-sensitiveな解析と エスケープ解析の援用 • any型の扱い • コールスタックなし大域脱出 • 値レベルの言語機能 実用上の話 • 解析速度と精度のトレードオフ • 配列はタプルかつシーケンス • 解析結果の説明性 • 解析未到達コードの解析 • バイトコード解析の制限 • 山ほどあるRuby言語機能 • 複雑すぎるRubyの引数 • オブジェクト指向の扱い • 組み込みライブラリの型 • 型レベル言語機能プラグイン 12 ※まだ未実装のネタも含みます
普通の型システムとの違い • 普通の型システムはメソッド単位で解析する • 仮引数の型はどうする? • 手書きする(TypeScript他) • 使われ方を見て決める(ML他) 13
普通の型システムとの違い • 普通の型システムはメソッド単位で解析する • 仮引数の型はどうする? • 手書きする(TypeScript他) • 我々の目的には合わない •
使われ方を見て決める(ML他) 13
普通の型システムとの違い • 普通の型システムはメソッド単位で解析する • 仮引数の型はどうする? • 手書きする(TypeScript他) • 我々の目的には合わない •
使われ方を見て決める(ML他) • 右のような例で難しい • 構造的部分型? 13 class A def foo = 42 end class B def foo = "str" end def f(n) n.foo #=> 何? end
型プロファイラの主要な問題点 • 解析が遅い(速度と精度のトレードオフ) • 解析の起点(テスト)が必要 • 未使用関数は引数anyとしか言えない • 誤推定は生じる •
解析結果の理解が難しい • 開発体験が未知 • いろいろ工夫を考えながらやってます 14
アジェンダ • Ruby 3の静的型解析の計画 • 型プロファイラ:Ruby 3の型推論器 • ➔デモ •
3Dトレーサ • 最長共通列ライブラリ • 型プロファイラ自身を型プロファイル • 今後の話(与太話) 15
いい感じのデモ:ao.rb • 3Dレイトレーサ • 300行のトイプログラム • Syoyo Fujita, Hideki Miura作
• https://code.google.com/ archive/p/aobench/ • 若干改変あり 16
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
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ベクトルのクラス
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ベクトルのクラス 座標
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
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
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
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つの球
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つの球 球は中心と半径
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
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 光線は起点と方向
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 光線は起点と方向 交点の判定・計算
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相当
Demo: ao.rb • いい感じでは? 20
事例: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]) {}
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 誤推定っぽいのは薄くしてます
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 列の中の要素 誤推定っぽいのは薄くしてます
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 列の中の要素 追加削除の位置 誤推定っぽいのは薄くしてます
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に書き換えた 誤推定っぽいのは薄くしてます
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
diff-lcsで出た警告の例 • flow-sensitiveな解析が必要 ※実際にはもっといっぱい出てます 個別に原因究明して、修正や改善検討をする…… 24 if callbacks.respond_to?(:finished_a) and …
… callbacks.finished_a(event) #=>「NilClass#finished_aを呼ぶかも」警告が出る … else
事例:type-profiler • 型プロファイラのコード • Rubyで書かれている(5000行くらい)ので • 型プロファイルできる 25
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
type-profilerいろいろ問題点(抜粋) • 再帰データ構造の扱いが微妙 • 巨大なUnionが出てきてつらい • 継承関係を利用してまとめる? • 型のエイリアスをうまく作る? •
Object#method経由の呼び出しが追えない • 作り込みが足らない ※実際にはもっといっぱい(略) 27 (TP::Type | TP::Type::Any | TP::Type::Array | … | any)
FAQ • X | any は any と同じでは? • おっしゃるとおり
• でも敵術のプロトタイプ生成には便利なので あえて潰さずに残している 28
アジェンダ • Ruby 3の静的型解析の計画 • 型プロファイラ:Ruby 3の型推論器 • デモ •
➔今後の話(与太話) 29
polyglot • 2つ(以上)の言語で解釈可能なプログラム • 例:RubyとJavaScriptのpolyglot • コツ:片方の言語だけで解釈されるように頭を使う 30 if (0)
print("Hello Ruby") [ここはRuby] else console.log("Hello JS") [ここはJS] // end
型付き言語のプログラムはpolyglot • インタプリタが動的な意味で解釈できる • さらに、型システムが静的な意味で解釈できる 31
型付き言語のプログラムはpolyglot • インタプリタが動的な意味で解釈できる • さらに、型システムが静的な意味で解釈できる • 型注釈はチート(polyglotの観点では) • 型システムだけに指示を与えられるずるい記述 31
型プロファイラの開発体験? • ずるくないpolyglot • 普通の実行に加え、型レベル実行を意識して書く • 別言語ではないので、そこまで辛くはない(はず) 32
型プロファイラの開発体験? • ずるくない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.
型プロファイラの開発体験? • ずるくない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.
進捗と今後 • 現状:やっとスタート地点 • 解析器の基本設計ができた • Rubyのおおよその言語機能がサポートできてきた • 組み込みクラスの知識をRBSから取り込んだ •
今後:実験と改善を繰り返す • バグの洗い出しと修正 • プログラミング体験の設計と不足機能の実装 • 診断機能、差分更新機能 • Railsアプリ解析用のドライバ開発 • など 33
謝辞 • 三浦さん(mrubyでの先行研究事例) • soutaroさん(RBS、その他議論) • uenoBさんとesumiiさん(アドバイス多数) • matz、akr、ko1などのRuby開発者たち •
Sorbetの人たち(Stripe, Shopifyなど) • Jeff Foster(10年の既存研究、別方法の型推論) 34
まとめ • Ruby 3は型記述言語・型推論・型検査を 提供したい • 型推論担当の型プロファイラをやってます • (寛容な心で)手伝ってくれる人たのむ! •
https://github.com/mame/ruby-type-profiler 35
説明しなかったこと • オーバーロードの推定は諦めた • 爆発する • オーバーロードするときは基本的に手書きして • 再帰呼び出しはいい感じにできる •
でも再帰的なデータ構造のハンドリングは微妙 • カスタムメソッド • 型プロファイラプラグイン • インスタンス変数の配列の破壊 • を説明するには、まずコンテナ型がメソッドを跨がらな いことを説明しないと…… 36