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
August 10, 2021
Programming
0
29
Ruby 3の型解析に向けた計画
大阪Ruby会議02
Yusuke Endoh
August 10, 2021
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
PSR-15 はあなたのための ものではない? - phpcon2024
myamagishi
0
100
create_tableをしただけなのに〜囚われのuuid編〜
daisukeshinoku
0
240
Beyond ORM
77web
5
520
ブラウザ単体でmp4書き出すまで - muddy-web - 2024-12
yue4u
2
460
たのしいparse.y
ydah
3
120
StarlingMonkeyを触ってみた話 - 2024冬
syumai
3
270
良いユニットテストを書こう
mototakatsu
5
2k
命名をリントする
chiroruxx
1
390
わたしの星のままで一番星になる ~ 出産を機にSIerからEC事業会社に転職した話 ~
kimura_m_29
0
180
Fibonacci Function Gallery - Part 1
philipschwarz
PRO
0
210
RWC 2024 DICOM & ISO/IEC 2022
m_seki
0
210
アクターシステムに頼らずEvent Sourcingする方法について
j5ik2o
4
250
Featured
See All Featured
XXLCSS - How to scale CSS and keep your sanity
sugarenia
247
1.3M
Building Applications with DynamoDB
mza
91
6.1k
We Have a Design System, Now What?
morganepeng
51
7.3k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
229
52k
Agile that works and the tools we love
rasmusluckow
328
21k
The MySQL Ecosystem @ GitHub 2015
samlambert
250
12k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
507
140k
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
191
16k
Embracing the Ebb and Flow
colly
84
4.5k
jQuery: Nuts, Bolts and Bling
dougneiner
61
7.5k
KATA
mclloyd
29
14k
Dealing with People You Can't Stand - Big Design 2015
cassininazir
365
25k
Transcript
Ruby 3の型解析 に向けた計画 遠藤 侑介 大阪Ruby会議02 1
自己紹介:遠藤侑介 (@mametter) • クックパッドで働く フルタイムRubyコミッタ • テスト、CIの番人 • キーワード引数 粉砕
整理 • Ruby 3の静的解析 • クックパッドに興味あったら お声がけください 2
余談:Ruby3キーワード引数の変更 Ruby2では「最後の引数=キーワード引数」だった Ruby3ではこれらが禁止される予定! • キーワード引数はキーワード引数として受け渡ししよう • オプショナル引数はハッシュとして受け渡ししよう 3 def foo(kw:
1); end foo(kw: 42) def foo(kw:1); end foo(**hash) def foo(kw: 1) end foo({ kw: 42 }) def foo(kw: 1) end foo(hash)
余談:Ruby2.7のキーワード引数 •直すべきメソッド呼出しにレベル1警告が出ます • 詳細な案内は追ってどこかに書きます • まだ詳細な仕様が決まっていない(委譲を調整中) • Railsはだいたい対応済み (!?) 4
def foo(kw: 1) end foo({ kw: 42 }) test.rb:3: warning: The last argument is used as the keyword parameter test.rb:1: warning: for `foo' defined here
この発表では • Ruby 3の静的解析の構想と進捗 •型プロファイラの設計と実装 • の、細かくて難しい面をはじめて話します 5
質問:型注釈 書きたいですか? 6 def increment: (Integer) -> Integer def increment(n)
n + 1 end ソースコード 型注釈
質問:型注釈 書きたいですか? 🙋 • 書きたくないし、他人にも書いてほしくない • 書きたくないが、他人はどちらでもいい • 書きたくないが、他人には書いてほしい •
書きたい 7
Ruby 3の静的解析の構想 •目的: バグっぽいコードを指摘する • 要件: Rubyのプログラミング体験を維持する (自分で)型を書かない選択肢を残したい •構想 1.
標準の型シグネチャ言語 2. 型シグネチャなし型検査+シグネチャ推定 3. 型シグネチャあり型検査 8
1. 型シグネチャフォーマット(.rbs) Rubyコードの型情報を示す標準形式 9 class Array[X] < Object include Enumerable
def []: (Integer) -> X? def []=: (Integer, X) -> X def each: () { (X) -> void } -> Array[X] ... end 組み込みメソッドの.rbsをRuby 3に同梱予定 コントリビューションチャンス! github.com/ruby/ruby-signature
2. 型シグネチャなし検査+推定 無注釈コードの緩い型検査+型シグネチャ推定 def foo(n) n + "s" end def
bar(n) ary = [1, "S"] ary[n] end foo(gets.to_i) bar(gets.to_i) 10 型プロファイラ開発中 github.com/mame/ruby-type-profiler def bar: (Int) -> (Int | Str) TypeError: failed to resolve Integer#+(String) mruby 向けには mruby-meta-circular も
3. 型シグネチャあり型検査 型シグネチャとコードの整合性を検査する class Foo def foo(s) s + 42
end def bar(s) s.gsuub(//,"") end end class Foo def foo:(Str)->Int def bar:(Str)->Int end 11 TypeError! Str + Int NoMethod Error! Steep github.com/soutaro/steep Sorbet github.com/sorbet/sorbet 整合?
Ruby 3の方向性 •ライブラリ作者 .rbs 書いてください🙏 (型プロファイラの推定機能でサポートはしたい) • アプリ作者 • 注釈書かず、検査もいらない
→ Ruby 2と同じ • 注釈を書いてしっかり検査 → Steep/Sorbet等 • 注釈を書かず、緩く検査したい→型プロファイラ! 12
型プロファイラの話 13
型プロファイラとは •目的:(アプリの)型シグネチャなしで • ざっくり型エラーを探す • 型シグネチャのプロトタイプを生成する • 方法:プログラムを型レベルで実行する • ライブラリの型シグネチャ
• アプリのテスト は必要 14
型プロファイラの動作イメージ Rubyコードを「型レベル」で実行する 普通のインタプリタ def foo(n) n.to_s end foo(42) Calls w/
42 Returns "42" 型プロファイラ def foo(n) n.to_s end foo(42) Calls w/ Integer Returns String Object#foo :: (Integer) -> String 15
型プロファイラと分岐 実行を「フォーク」する def foo(n) if n < 10 n else
"error" end end foo(42) Fork! イマココ n<10 の真偽は わからない Object#foo :: (Integer) -> (Integer | String) 16 Returns String Returns Integer
どのくらいできている? •型プロファイラ自身が5秒で解析できる • 2000行程度の普通のRubyコード •optcarrotが3秒で解析できる • 5000行程度の普通のRubyコード 17
例:ユーザ定義クラス class Foo end class Bar def make_foo Foo.new end
end Bar.new.make_foo Type Profiler Bar#make_foo :: () -> Foo
例:インスタンス変数 class Foo attr_accessor :ivar end Foo.new.ivar = 42 Foo.new.ivar
= "STR" Foo.new.ivar Type Profiler Foo#@ivar :: Integer | String Foo#ivar= :: (Integer) -> Integer Foo#ivar= :: (String) -> String Foo#ivar :: () -> (String | Integer)
例:ブロック def foo(x) yield 42 end s = "str" foo(1)
do |x| s end Type Profiler Object#foo :: (Integer, &Proc[(Integer) -> String]) -> String
例:再帰関数 def fib(n) if n > 1 fib(n-1) + fib(n-2)
else n end end fib(10000) Type Profiler Object#fib :: (Integer) -> Integer
型プロファイラの何が難しいか? • 真似できる成功事例がない 抽象解釈・記号実行 長年研究されてきたが😟 • スケーラビリティと精度のトレードオフが難しい 型プロファイラは見逃しも誤検出も許容する •実装がとにかく地味に大変 フルセットのRubyインタプリタ+型レベル評価設計
• 「コンテナ型」が扱いづらい 今日のメイン 22
コンテナ型とは? •配列やハッシュなど、他の型を要素に持つ型 • 配列を中心に説明します •問題:型プロファイラで配列をどう扱うか? 23 a = [1, "str",
true] p a[0] #=> Integer p a[1] #=> String p a[2] #=> Boolean
解決策1:単なる「Array」型 •例えばIntegerの場合 •問題点:要素の型が出てくると破滅 24 n = 1 # Integer型 n.times
{|i| } # Integer#timesとわかる a = [1] # Array型 a[0] # 要素の型は不明(any型) a[0].times {|i| } # 何もわからない a[0].tmes {|i| } # 警告もできない
解決策2: ジェネリクス? •要素の型を持つ型 • 問題点:破壊的変更があると型が変わる! a = [1] # Array<Integer>と推定
a[0] # Integerとわかる a[0].times {|i| } # Integer#timesとわかる a = [1] # Array<Integer>と推定 a << "str" # Array<Integer|String>に変わる!
解決策2: ジェネリクス?(続き) • 型プロファイラでは値レベルの区別がない 26 a = [1] # Array<Int>
b = [1] # Array<Int>(aと完全に同じ型?) a[0] = "str" # Array<Int>がArray<Str>に変更?? b[0] # String?? a = [1] # Array<Int> b = a # Array<Int>(aと同じ型) a[0] = "str" # Array<Int>がArray<Str>に変更 b[0] # String
解決策3:値レベルの区別を入れる •ナイーブにやると解析が有限時間で止まらない • わりと研究中の分野(separation logic) • 一方 Sorbet は型注釈を使った 27
a = T.let([1], T::Array[T.any(Integer, String)]) b = a a[0] = "str" b[0] # String | Integer
型プロファイラの現在の設計 (1) 配列ができた位置(allocation site)で区別 完璧ではないがわりとよくある妥協 28 1: a = [1]
# Array<1行目> # 1行目→Int 2: b = [1] # Array<2行目> # 1行目→Int 2行目→Int 3: a[0] = "str" # 1行目→Str 2行目→Int 4: b[0] # Array<2行目>の要素はInt 1: a = [1] # Array<1行目> # 1行目→Int 2: b = a # Array<1行目> # 1行目→Int 3: a[0] = "str" # 1行目→Str 4: b[0] # Array<1行目>の要素はStr
設計 (1) の問題 29 1: class Foo 2: def to_a
3: [42] # Array<3行目> 4: end 5: end 6: a = Foo.new.to_a # Array<3行目> 7: b = Foo.new.to_a # Array<3行目> 8: a[0] = "str" 9: b[0] # String??
型プロファイラの現在の設計 (2) メソッドは超える時は位置情報を失うとする 30 1: class Foo 2: def to_a(a)
3: [42] # Array<3行目> 4: end 5: end 6: a = Foo.new.to_a # Array<6行目>(3行目ではない) 7: b = Foo.new.to_a # Array<7行目>(3行目ではない)
型プロファイラの現在の設計 (3) • タプル型:長さ固定、各要素を区別する 31 ary = [1, "str"] #
[Int, Str] ary[0] # 0番目はInt ary[0].times {|i| } # IntなのでInt#timesとわかる ary = [1] + ["str"] # Array[Int | Str] ary[0] # Int | Str ary[0].times {|i| } # Str#timesも呼ぶかも、と警告 •シーケンス型:長さ不明、全要素をまとめる
現在の設計 (3) •リテラル型:元のリテラルの値を持つ型 32 0 # Literal<0, Int> ary[0] #
リテラル型なので0とわかる def access(a, n) a[n] # nはIntなので、aryのどこを読むかは不明 end access([1, "STR"], 0) #=> Int|Str # Literal<0, Int>だがメソッドには渡らない リテラル型もメソッド境界は超えない
配列型の実装の細々した問題 型の領域が無限になる • Array<Int> • Array<Array<Int>> • Array<Array<Array<Int>>> • …
• 適当な深さで打ち切る予定 33 a = 42 while true a = [a] end
可変長引数のサポート 配列ができたら(一応)やるだけ 34 def foo(a, *r, z) end foo(1, 1,
"str", 1) foo: (Int, Array<Int|Str>, Int) -> NilClass def foo(a, b, c) end ary = [42] + ["str"] foo(*ary) foo: (Int|Str, Int|Str, Int|Str) -> NilClass
余談:既知のバグ 35 def foo(a, b, c) end ary = [1]
foo(1, *ary, "str") foo:(Int, Int, Str)->Nil foo:(Int, Int|Str, Int|Str)->Nil 期待 実際 foo(1, *ary, "str") foo(1, *(ary.dup+["str"])) は のように動く 理由:YARVバイトコードの実装の都合
関連研究 •mruby-meta-circular (Hideki Miura) • 型プロファイラの元ネタ • Type Analysis for
JavaScript (Jensen, et al.) • pytype (Google's unofficial project) • 型解析のための抽象解釈器の事例 36
まとめ •Ruby 3の静的解析の構想を説明しました • 型プロファイラの難しみ(コンテナ型)を 説明しました • いろいろ悩みながらやってます • 協力者募集中!
https://github.com/mame/ruby-type-profiler 37