More Related Content Similar to Ruby 3の型推論やってます (20) Ruby 3の型推論やってます6. 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
7. 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つの球
球は中心と半径
8. 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相当
16. 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に書き換えた
誤推定っぽいのは薄くしてます
17. 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
21. 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を呼ぶかも」警告が出る
23. FAQ
• X | any は any と同じでは?
• おっしゃるとおり
• でもRBSプロトタイプ生成には便利なので
あえて潰さずに残している
23
26. 扱う型:コンテナ周り
• 具体型: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
27. 扱う型:その他
• Proc型: ブロック、具体値として扱う
• callやyieldをなるべく正確にトレースするため
• Union型: 型の和集合の型
• (Integer | String) : 整数か文字列
• 工夫(というか制限)
• ([*Integer] | [*String])は[*(Integer|String)]に潰す
• 不正確だけど、組合せ爆発を避けて解析高速化を選んだ
27
29. 可変長引数とキーワード引数
• なんかそれっぽく動く(実装は地味に大変だった)
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}
31. 「ダミー実行」(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で解決できる?
33. 困難に対して
• 解析が遅い ➔ (精度を犠牲にしつつ)高速化してきた
• テストが必要 ➔ (荒削りだけど)ダミー実行
• 誤推定・誤検出 ➔ (限界はあるけど)改善中
• 解析の理解が難しい ➔ (簡単だけど)診断機能
• 開発体験が未知 ➔ ?
33
38. 進捗と今後
• 現状:やっとスタート地点
• 解析器の基本設計ができた
• Rubyのおおよその言語機能がサポートできてきた
• 組み込みクラスの知識をRBSから取り込んだ
• 今後:実験と改善を繰り返す
• バグの洗い出しと修正
• プログラミング体験の設計と不足機能の実装
• 診断機能、差分更新機能
• Railsアプリ解析用のドライバ開発
• など
38
40. 説明しなかったこと
• オーバーロードの推定は諦めた
• 爆発する
• オーバーロードするときは基本的に手書きして
• 再帰呼び出しはいい感じにできる
• でも再帰的なデータ構造のハンドリングは微妙
• カスタムメソッド
• 型プロファイラプラグイン
• インスタンス変数の配列の破壊
• を説明するには、まずコンテナ型がメソッドを跨がらな
いことを説明しないと……
40