More Related Content Similar to Ruby でつくる型付き Ruby Similar to Ruby でつくる型付き Ruby (20) Ruby でつくる型付き Ruby2. 自己紹介:@mametter(遠藤侑介)
• Ruby コミッタ(2008年~)
• Ruby への主な貢献
– Ruby 本体のテストや
RubySpec を増強した
– カバレッジ測定機能を実装した
• Ruby 2.5で分岐カバレッジ実装予定!
くわしくは RubyKaigi 2017@広島(9/18~20)で
– リリース管理に関わった(特に Ruby 1.9.2 と 2.0)
– キーワード引数やデッドロック検出などを実装した
’06下 ’07上 ’07下 ’08上
60
70
80
90
100
coverage(%)
70%
85%
C0カバレッジ遷移
4. 今日のテーマ
• Ruby でつくる Ruby
Quine
とか変なプログラム
の本
インタプリタ
をRubyで
自作してみる本
型システム
の教科書
(”TAPL”の翻訳)
代入禁止
プログラミングの本
(”PFDS”の翻訳)
型システム
の教科書
(”TAPL”の翻訳)
型付き Ruby
8. みんな Ruby インタプリタ書こう!
• プログラマの教養として、自分の好きな言語の
インタプリタくらい作っておきたい!
– 実用レベルじゃなくてよい
– SchemeやMLの入門では定番の教材
• ガイドブックを書いた
– 144 ページ、128 行で
インタプリタが書ける!
– 対象読者:プログラミング未経験者
から Ruby 経験者まで
9. 目次
• 『Ruby でつくる Ruby』ダイジェスト
– 概要 今からここ
– インタプリタの構成と実装例
– ゴールと意義
• Ruby でつくる型付き Ruby
– Ruby と型の概観
– 漸進的型付けのアイデアと実装例
• まとめ
12. 本書の内容
• Ruby 言語で Ruby インタプリタを書いてみる
– 「Ruby プログラムを読んで、解釈し、実行する」
– という Ruby プログラムを書く
• (eval は使わない)
• 疑問:作ったインタプリタはどうやって動かす?
– 答え:Matz 製のインタプリタに動かしてもらう
14. 実際に作るもの
• MinRuby:Ruby のコア部分を切り出した言語
– 演算式(1 + 2 とか)
– 変数(x = 1 とか p(x) とか)
– 分岐とループ(if と while)
– 関数、配列とハッシュ
– オブジェクト指向やブロックは不必要(!)なので省く
• 本書の正確な内容:MinRuby で MinRuby インタ
プリタを作る
– インタプリタの実装も楽になる
– 覚えることが少ない(プログラミング未経験者でも読める!)
15. 目次
• 『Ruby でつくる Ruby』ダイジェスト
– 概要
– インタプリタの構成と実装例 今からここ
– ゴールと意義
• Ruby でつくる型付き Ruby
– Ruby と型の概観
– 漸進的型付けのアイデアと実装例
• まとめ
18. Ruby での木の表現方法
• ["演算子", 左の枝, 右の枝]
という配列を入れ子にして表現する
["+",
1,
["*",
2,
3
]
]
["+",
["lit", 1],
["*",
["lit", 2],
["lit", 3],
]
]
22. つづきは本書で
• 言語機能をガンガン足していく
– 5章:複文、変数代入・参照
– 6章:if 文、while 文
– 7、8章:関数呼び出し、関数定義
– 9章:配列やハッシュの作成・参照・代入
MinRuby インタプリタの出来上がり
– 執筆時に工夫したこと
• 各章で一旦完結させる
– 動作しない状態で
終わらない
– 演習問題を入れる
• 退屈にならないようにする
– 実装対象の言語機能を考察する
– 環境などの必要な拡張をしたり
– each とか使ってない
(ブロックがないので使えない)
23. MinRuby インタプリタ全貌
require "minruby"
def evaluate(exp, genv, lenv)
case exp[0]
when "stmts"
last = nil
i = 1
while exp[i]
last = evaluate(exp[i], genv, lenv)
i = i + 1
end
last
when "lit"
exp[1]
when "+"
evaluate(exp[1], genv, lenv) + evaluate(exp[2], genv, lenv)
when "-"
evaluate(exp[1], genv, lenv) - evaluate(exp[2], genv, lenv)
when "*"
evaluate(exp[1], genv, lenv) * evaluate(exp[2], genv, lenv)
when "/"
evaluate(exp[1], genv, lenv) / evaluate(exp[2], genv, lenv)
when "%"
evaluate(exp[1], genv, lenv) % evaluate(exp[2], genv, lenv)
when "=="
evaluate(exp[1], genv, lenv) == evaluate(exp[2], genv, lenv)
when "<"
evaluate(exp[1], genv, lenv) < evaluate(exp[2], genv, lenv)
when "<="
evaluate(exp[1], genv, lenv) <= evaluate(exp[2], genv, lenv)
when ">"
evaluate(exp[1], genv, lenv) > evaluate(exp[2], genv, lenv)
when ">="
evaluate(exp[1], genv, lenv) >= evaluate(exp[2], genv, lenv)
when "var_ref"
lenv[exp[1]]
when "var_assign"
lenv[exp[1]] = evaluate(exp[2], genv, lenv)
when "if"
if evaluate(exp[1], genv, lenv)
evaluate(exp[2], genv, lenv)
else
evaluate(exp[3], genv, lenv) if exp[3]
end
when "while"
while evaluate(exp[1], genv, lenv)
evaluate(exp[2], genv, lenv)
end
when "func_def"
genv[exp[1]] = ["user_defined", exp[2], exp[3]]
when "func_call"
args = []
i = 0
while exp[i + 2]
args[i] = evaluate(exp[i + 2], genv, lenv)
i = i + 1
end
mhd = genv[exp[1]]
if mhd[0] == "builtin"
minruby_call(mhd[1], args)
else
new_lenv = {}
params = mhd[1]
i = 0
while params[i]
new_lenv[params[i]] = args[i]
i = i + 1
end
evaluate(mhd[2], genv, new_lenv)
end
when "ary_new"
ary = []
i = 0
while exp[i + 1]
ary[i] = evaluate(exp[i + 1], genv, lenv)
i = i + 1
end
ary
when "ary_assign"
ary = evaluate(exp[1], genv, lenv)
idx = evaluate(exp[2], genv, lenv)
val = evaluate(exp[3], genv, lenv)
ary[idx] = val
when "ary_ref"
ary = evaluate(exp[1], genv, lenv)
idx = evaluate(exp[2], genv, lenv)
ary[idx]
when "hash_new"
hsh = {}
i = 0
while exp[i + 1]
key = evaluate(exp[i + 1], genv, lenv)
val = evaluate(exp[i + 2], genv, lenv)
hsh[key] = val
i = i + 2
end
hsh
else
p("error")
pp(exp)
raise "unknown node: #{ exp[0] }"
end
end
genv = {
"p" => ["builtin", "p"],
"require" => ["builtin", "require"],
"minruby_parse" => ["builtin", "minruby_parse"],
"minruby_load" => ["builtin", "minruby_load"],
"minruby_call" => ["builtin", "minruby_call"],
}
lenv = {}
evaluate(minruby_parse(minruby_load()), genv, lenv)
空行含めて
無理なく 128 行
24. 目次
• 『Ruby でつくる Ruby』ダイジェスト
– 概要
– インタプリタの構成と実装例
– ゴールと意義 今からここ
• Ruby でつくる型付き Ruby
– Ruby と型の概観
– 漸進的型付けのアイデアと実装例
• まとめ
28. 目次
• 『Ruby でつくる Ruby』ダイジェスト
– 概要
– インタプリタの構成と実装例
– ゴールと意義
• Ruby でつくる型付き Ruby
– Ruby と型の概観 今からここ
– 漸進的型付けのアイデアと実装例
• まとめ
29. 型とは
• 変数の取りうる値の範囲を表現したもの
– この呼び出しは OK
– この呼び出しは NG
• 参考情報:Matz は Ruby 3 で型を入れたいと言っている
– RubyKaigi 2017 @ 広島 でも関連発表が複数ありそう
def add_int(x: Integer, y: Integer)
x + y
end 型注釈
add_int(1, 2)
add_int("foo", 2)
35. 目次
• 『Ruby でつくる Ruby』ダイジェスト
– 概要
– インタプリタの構成と実装例
– ゴールと意義
• Ruby でつくる型付き Ruby
– Ruby と型の概観
– 漸進的型付けのアイデアと実装例 今からここ
• まとめ
36. 漸進的型付け [Siek ‘06] のアイデア
• 型ありと型なしの同居を認める
– 型注釈があったら型チェックされる
– 型注釈がなかったら型チェックしない
• Ruby での使い方
– ダックタイピングしたいところには型注釈を書かない
– 型注釈のない既存資産もとりあえずそのまま使える
(気が向いたときに書き足してもよい)
def add(x, y)
x + y
end
def add_int(x:Integer, y:Integer)
x + y
end
37. 漸進的型付けの実装イメージ
• 型注釈がない変数は any 型とする
• any 型が絡む演算の結果は any 型とする
– すごく単純なアイデアだけど
2006 年頃にようやく登場
• 部分型(継承関係)を使った
アプローチが研究の泥沼だった
– 漸進的型付けは
Python 3 の型ヒントや
TypeScript の基盤でもある
(つまり多分 Ruby でも使える?)
J. G. Siek, et al. Gradual Typing for Functional Languages より引用
38. 実装してみた、デモ
def add(x, y)
x + y
end
def add_int(x:Integer, y:Integer)
x + y
end
p(add(1, 2)) #=> 3
p(add("foo", 2)) #=> 実行時例外
p(add("foo", "bar") #=> "foobar"
p(add_int(1, 2)) #=> 3
p(add_int("foo", 2)) #=> 実行前に型エラー
p(add_int("foo", "bar") #=> 実行前に型エラー
※型注釈を含む構文解析は自作しました
(本には未掲載、本で読みたい人はラムダノート社長の鹿野さんを煽ってください)
39. まとめ:型付き MinRuby
• MinRuby を実験用環境として使う
– クラスやブロックなど、難しいものがないので実験し
やすい
• 漸進的型付けの Ruby 実用化への課題
– クラスやブロックなど、難しいものにも対応しないと
いけない
• Python や TypeScript が行けたくらいだから多分できる?
– 型推論の併用を考える
• TypeScript は局所型推論みたいなことをやってる
(関数の引数の方は推論しない)
40. 目次
• 『Ruby でつくる Ruby』ダイジェスト
– 概要
– インタプリタの構成と実装例
– ゴールと意義
• Ruby でつくる型付き Ruby
– Ruby と型の概観
– 漸進的型付けのアイデアと実装例
• まとめ 今からここ
41. まとめ
• 『Ruby でつくる Ruby』
– Ruby 言語で
Ruby インタプリタを作る本
• この会場で直販中!
• インタプリタの原理と構成を
学べる
• Ruby 言語を冷静に見つめ、
機能拡張とかを考えられる
– 型とか