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
68
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Ruby 3の型解析に向けた計画
大阪Ruby会議02
Yusuke Endoh
August 10, 2021
More Decks by Yusuke Endoh
See All by Yusuke Endoh
Practical TypeProf: Lessons from Analyzing Optcarrot
mame
1
3.1k
型システムを知りたい人のための型検査器作成入門
mame
15
5.4k
TRICK 2025 Results
mame
0
5.7k
Writing Ruby Scripts with TypeProf
mame
1
1.4k
An Invitation to TRICK: How to write weird Ruby programs
mame
1
1.4k
TypeProf進捗
mame
0
120
12年前の『型システム入門』翻訳の思い出話
mame
14
2.8k
Good first issues of TypeProf
mame
4
10k
Revisiting TypeProf - IDE support as a primary feature
mame
1
3.8k
Other Decks in Programming
See All in Programming
komatsuna「分散システムにおけるバグ分析手法」
komatsunaqa
0
130
そこに3びきプロダクトがいるじゃろう——生成AI時代における“価値が届かない理由”の構造
kosuket
0
280
광주소프트웨어마이스터고등학교 DevFest 특강 - 바이브 코딩 시대에서 주니어 개발자로 살아남는 방법
utilforever
1
160
PHP初心者セッション2026 〜生成AIでは見えない裏側を知る:今だからLAMPを通して仕組みを学ぶ〜
kashioka
0
740
The Past, Present, and Future of Enterprise Java
ivargrimstad
0
440
AIエージェントで 変わるAndroid開発環境
takahirom
2
740
為什麼你並不需要ViewModel / No, you don't need a ViewModel
lovee
1
450
5分で問診!Composer セキュリティ健康診断
codmoninc
0
700
Laravel Boostに学ぶ、AIにPHPを書かせる技術 〜OSSの実装から蒸留するエージェント制御の王道〜
kentaroutakeda
3
560
PHPだって関数型したい 〜できること、できないこと〜 / fp-in-php
jsoizo
1
260
2年かけて Deno に DOMMatrix を実装した話 / How I implemented DOMMatrix in Deno over two years
petamoriken
0
180
言語を使う側から、作る側へ。 自作 Lisp で得た新たな気づき。
andpad
0
140
Featured
See All Featured
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
2.9k
Typedesign – Prime Four
hannesfritz
42
3.1k
エンジニアに許された特別な時間の終わり
watany
108
250k
How to Get Subject Matter Experts Bought In and Actively Contributing to SEO & PR Initiatives.
livdayseo
0
160
Dominate Local Search Results - an insider guide to GBP, reviews, and Local SEO
greggifford
PRO
0
230
Done Done
chrislema
186
16k
The Curse of the Amulet
leimatthew05
2
13k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
25
2k
Claude Code のすすめ
schroneko
67
230k
Reality Check: Gamification 10 Years Later
codingconduct
0
2.2k
How to Build an AI Search Optimization Roadmap - Criteria and Steps to Take #SEOIRL
aleyda
1
2.1k
Navigating Weather and Climate Data
rabernat
0
420
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