Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ruby でつくる型付き Ruby

Yusuke Endoh
September 07, 2017

Ruby でつくる型付き Ruby

Yusuke Endoh

September 07, 2017
Tweet

More Decks by Yusuke Endoh

Other Decks in Programming

Transcript

  1. 自己紹介:@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カバレッジ遷移
  2. 今日のテーマ • Ruby でつくる Ruby Quine とか変なプログラム の本 インタプリタ をRubyで

    自作してみる本 型システム の教科書 (”TAPL”の翻訳) 代入禁止 プログラミングの本 (”PFDS”の翻訳) 型システム の教科書 (”TAPL”の翻訳) 型付き Ruby
  3. みんな Ruby インタプリタ書こう! • プログラマの教養として、自分の好きな言語の インタプリタくらい作っておきたい! – 実用レベルじゃなくてよい – SchemeやMLの入門では定番の教材

    • ガイドブックを書いた – 144 ページ、128 行で インタプリタが書ける! – 対象読者:プログラミング未経験者 から Ruby 経験者まで
  4. 目次 • 『Ruby でつくる Ruby』ダイジェスト – 概要  今からここ –

    インタプリタの構成と実装例 – ゴールと意義 • Ruby でつくる型付き Ruby – Ruby と型の概観 – 漸進的型付けのアイデアと実装例 • まとめ
  5. 本書の内容 • Ruby 言語で Ruby インタプリタを書いてみる – 「Ruby プログラムを読んで、解釈し、実行する」 –

    という Ruby プログラムを書く • (eval は使わない) • 疑問:作ったインタプリタはどうやって動かす? – 答え:Matz 製のインタプリタに動かしてもらう
  6. 実際に作るもの • MinRuby:Ruby のコア部分を切り出した言語 – 演算式(1 + 2 とか) –

    変数(x = 1 とか p(x) とか) – 分岐とループ(if と while) – 関数、配列とハッシュ – オブジェクト指向やブロックは不必要(!)なので省く • 本書の正確な内容:MinRuby で MinRuby インタ プリタを作る – インタプリタの実装も楽になる – 覚えることが少ない(プログラミング未経験者でも読める!)
  7. 目次 • 『Ruby でつくる Ruby』ダイジェスト – 概要 – インタプリタの構成と実装例 

    今からここ – ゴールと意義 • Ruby でつくる型付き Ruby – Ruby と型の概観 – 漸進的型付けのアイデアと実装例 • まとめ
  8. ? インタプリタの構成 入力 MinRuby プログラムの テキストファイル 出力 プログラムの 実行結果 1+2*3

    7 構文解析 計算の木 (抽象構文木) 評価 プログラム文字列を 「木」に変換する (ライブラリ使用) 「木」を見て 計算を行う
  9. インタプリタ実装の進め方 1. 実装する言語機能の抽象構文木を観察する 2. 解釈の方法をプログラムに落としていく • 例:if 文 – 抽象構文木:[“if”,

    条件式, then部分, else部分] – インタプリタ実装: – if 文の実装に if を使う インタプリタの本質は ターゲット言語からホスト言語への丸投げ if evaluate(tree[1]) evaluate(tree[2]) else evaluate(tree[3]) end
  10. つづきは本書で • 言語機能をガンガン足していく – 5章:複文、変数代入・参照 – 6章:if 文、while 文 –

    7、8章:関数呼び出し、関数定義 – 9章:配列やハッシュの作成・参照・代入 MinRuby インタプリタの出来上がり – 執筆時に工夫したこと • 各章で一旦完結させる – 動作しない状態で 終わらない – 演習問題を入れる • 退屈にならないようにする – 実装対象の言語機能を考察する – 環境などの必要な拡張をしたり – each とか使ってない (ブロックがないので使えない)
  11. 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 行
  12. 目次 • 『Ruby でつくる Ruby』ダイジェスト – 概要 – インタプリタの構成と実装例 –

    ゴールと意義  今からここ • Ruby でつくる型付き Ruby – Ruby と型の概観 – 漸進的型付けのアイデアと実装例 • まとめ
  13. できたもの • MinRuby で書かれた MinRuby インタプリタ  インタプリタ自身も MinRuby プログラム

    インタプリタがインタプリタ自身を実行できる! (ブートストラップ)
  14. RubyでRubyインタプリタを書く意義 • 実用上は無意味 – 機械語で書かれていないインタプリタは役に立たない – 世の中、意味のないことのほうが面白い • インタプリタの原理と構成を理解できる –

    教科書的な知識だけでなく実際に手を動かすのは重要 – ブートストラップに成功したときの達成感は半端ない • Ruby 言語自体を深く考察する土台になる – たとえば型とか
  15. 目次 • 『Ruby でつくる Ruby』ダイジェスト – 概要 – インタプリタの構成と実装例 –

    ゴールと意義 • Ruby でつくる型付き Ruby – Ruby と型の概観  今からここ – 漸進的型付けのアイデアと実装例 • まとめ
  16. 型とは • 変数の取りうる値の範囲を表現したもの – この呼び出しは 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)
  17. 型の目的 • 関数引数の想定を簡潔にドキュメント化したい – うざい • テストなしで自動的に『バグ』を見つけたい – テストで 1

    時間待ちたくない sleep 3600 add("foo", 2) # 引数 x と y は Integer を受け付けます def add(x, y) x + y end
  18. 型で見つけたい『バグ』とは? • この関数に対しては – これにバグはない – ほぼ明らかにバグ – OKかも?(ダックタイピング) –

    バグかどうかはプログラム単体では一意に決まらない – プログラマの意図に依存する def add(x, y) x + y end add(1, 2) add("foo", 2) add("foo", "bar")
  19. ダックタイピングを許す型注釈の例 • 解決1:オーバーロード – 許したい型が増えたらドンドンうざくなる • 解決2:構造的型付け – 型が長く難しくなりがち •

    解決3:漸進的型付け def add(x:Integer, y:Integer); x+y; end def add(x: String, y: String); x+y; end def add(x:α, y:α) where α.respond_to?(:+) x+y end
  20. 目次 • 『Ruby でつくる Ruby』ダイジェスト – 概要 – インタプリタの構成と実装例 –

    ゴールと意義 • Ruby でつくる型付き Ruby – Ruby と型の概観 – 漸進的型付けのアイデアと実装例  今からここ • まとめ
  21. 漸進的型付け [Siek ‘06] のアイデア • 型ありと型なしの同居を認める – 型注釈があったら型チェックされる – 型注釈がなかったら型チェックしない

    • Ruby での使い方 – ダックタイピングしたいところには型注釈を書かない – 型注釈のない既存資産もとりあえずそのまま使える (気が向いたときに書き足してもよい) def add(x, y) x + y end def add_int(x:Integer, y:Integer) x + y end
  22. 漸進的型付けの実装イメージ • 型注釈がない変数は any 型とする • any 型が絡む演算の結果は any 型とする

    – すごく単純なアイデアだけど 2006 年頃にようやく登場 • 部分型(継承関係)を使った アプローチが研究の泥沼だった – 漸進的型付けは Python 3 の型ヒントや TypeScript の基盤でもある (つまり多分 Ruby でも使える?) J. G. Siek, et al. Gradual Typing for Functional Languages より引用
  23. 実装してみた、デモ 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") #=> 実行前に型エラー ※型注釈を含む構文解析は自作しました (本には未掲載、本で読みたい人はラムダノート社長の鹿野さんを煽ってください)
  24. まとめ:型付き MinRuby • MinRuby を実験用環境として使う – クラスやブロックなど、難しいものがないので実験し やすい • 漸進的型付けの

    Ruby 実用化への課題 – クラスやブロックなど、難しいものにも対応しないと いけない • Python や TypeScript が行けたくらいだから多分できる? – 型推論の併用を考える • TypeScript は局所型推論みたいなことをやってる (関数の引数の方は推論しない)
  25. 目次 • 『Ruby でつくる Ruby』ダイジェスト – 概要 – インタプリタの構成と実装例 –

    ゴールと意義 • Ruby でつくる型付き Ruby – Ruby と型の概観 – 漸進的型付けのアイデアと実装例 • まとめ  今からここ
  26. まとめ • 『Ruby でつくる Ruby』 – Ruby 言語で Ruby インタプリタを作る本

    • この会場で直販中! • インタプリタの原理と構成を 学べる • Ruby 言語を冷静に見つめ、 機能拡張とかを考えられる – 型とか