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

Ruby でつくる型付き Ruby

Avatar for Yusuke Endoh Yusuke Endoh
September 07, 2017

Ruby でつくる型付き Ruby

Avatar for Yusuke Endoh

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 言語を冷静に見つめ、 機能拡張とかを考えられる – 型とか