Slide 1

Slide 1 text

Rubyでつくる 型付きRuby とちぎRuby会議07 (2017-08-26) @mametter(遠藤侑介)

Slide 2

Slide 2 text

自己紹介:@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カバレッジ遷移

Slide 3

Slide 3 text

自己紹介:@mametter(遠藤侑介) • いろいろ本を書いてます(翻訳を含む) Quine とか変なプログラム の本 インタプリタ をRubyで 自作してみる本 型システム の教科書 (”TAPL”の翻訳) 代入禁止 プログラミングの本 (”PFDS”の翻訳) ※翻訳歓迎!

Slide 4

Slide 4 text

今日のテーマ • Ruby でつくる Ruby Quine とか変なプログラム の本 インタプリタ をRubyで 自作してみる本 型システム の教科書 (”TAPL”の翻訳) 代入禁止 プログラミングの本 (”PFDS”の翻訳) 型システム の教科書 (”TAPL”の翻訳) 型付き Ruby

Slide 5

Slide 5 text

Ruby インタプリタって何? (matz製の) Rubyインタプリタ

Slide 6

Slide 6 text

みなさんに質問 Rubyインタプリタ 作ったことのある人?

Slide 7

Slide 7 text

もしここが「とちぎScheme会議」だったら • たぶんこうなる

Slide 8

Slide 8 text

みんな Ruby インタプリタ書こう! • プログラマの教養として、自分の好きな言語の インタプリタくらい作っておきたい! – 実用レベルじゃなくてよい – SchemeやMLの入門では定番の教材 • ガイドブックを書いた – 144 ページ、128 行で インタプリタが書ける! – 対象読者:プログラミング未経験者 から Ruby 経験者まで

Slide 9

Slide 9 text

目次 • 『Ruby でつくる Ruby』ダイジェスト – 概要  今からここ – インタプリタの構成と実装例 – ゴールと意義 • Ruby でつくる型付き Ruby – Ruby と型の概観 – 漸進的型付けのアイデアと実装例 • まとめ

Slide 10

Slide 10 text

プログラミング言語とは • コンピュータに対する指示を書くための言葉 コンピュータは Ruby プログラムを 直接理解できない ✕

Slide 11

Slide 11 text

インタプリタとは • プログラムの指示をコンピュータに伝えるプログ ラム – interpreter: 通訳 インタプリタは コンピュータが 理解できるように 書いておく インタプリタは プログラムを読んで 解釈し、実行する

Slide 12

Slide 12 text

本書の内容 • Ruby 言語で Ruby インタプリタを書いてみる – 「Ruby プログラムを読んで、解釈し、実行する」 – という Ruby プログラムを書く • (eval は使わない) • 疑問:作ったインタプリタはどうやって動かす? – 答え:Matz 製のインタプリタに動かしてもらう

Slide 13

Slide 13 text

つまり、こういうこと ※こんなものを作って 何の意味があるかは後述

Slide 14

Slide 14 text

実際に作るもの • MinRuby:Ruby のコア部分を切り出した言語 – 演算式(1 + 2 とか) – 変数(x = 1 とか p(x) とか) – 分岐とループ(if と while) – 関数、配列とハッシュ – オブジェクト指向やブロックは不必要(!)なので省く • 本書の正確な内容:MinRuby で MinRuby インタ プリタを作る – インタプリタの実装も楽になる – 覚えることが少ない(プログラミング未経験者でも読める!)

Slide 15

Slide 15 text

目次 • 『Ruby でつくる Ruby』ダイジェスト – 概要 – インタプリタの構成と実装例  今からここ – ゴールと意義 • Ruby でつくる型付き Ruby – Ruby と型の概観 – 漸進的型付けのアイデアと実装例 • まとめ

Slide 16

Slide 16 text

? インタプリタの構成 入力 MinRuby プログラムの テキストファイル 出力 プログラムの 実行結果 1+2*3 7 構文解析 計算の木 (抽象構文木) 評価 プログラム文字列を 「木」に変換する (ライブラリ使用) 「木」を見て 計算を行う

Slide 17

Slide 17 text

Ruby での木の表現方法 • ["演算子", 左の枝, 右の枝] という配列を入れ子にして表現する ["+", 1, ["*", 2, 3 ] ]

Slide 18

Slide 18 text

Ruby での木の表現方法 • ["演算子", 左の枝, 右の枝] という配列を入れ子にして表現する ["+", 1, ["*", 2, 3 ] ] ["+", ["lit", 1], ["*", ["lit", 2], ["lit", 3], ] ]

Slide 19

Slide 19 text

評価のイメージ • 枝を切って 葉に差し替える • これを繰り返す • 葉だけに なったら終了

Slide 20

Slide 20 text

評価器の実装 • 関数の再帰呼び出しを使う – 本書における「関数」の定義=木を辿るための道具 再帰の初心者向け解説は、フィボナッチより木のトラバースの方がわかり やすいのではないだろうか(木という目に見える再帰構造があるので) def evaluate(tree) if tree[0] == "lit" tree[1] elsif tree[0] == "+" evaluate(tree[1]) + evaluate(tree[2]) elsif tree[0] == "*" evaluate(tree[1]) * evaluate(tree[2]) end end “演算子”の部分 左の枝 右の枝

Slide 21

Slide 21 text

インタプリタ実装の進め方 1. 実装する言語機能の抽象構文木を観察する 2. 解釈の方法をプログラムに落としていく • 例:if 文 – 抽象構文木:[“if”, 条件式, then部分, else部分] – インタプリタ実装: – if 文の実装に if を使う インタプリタの本質は ターゲット言語からホスト言語への丸投げ if evaluate(tree[1]) evaluate(tree[2]) else evaluate(tree[3]) end

Slide 22

Slide 22 text

つづきは本書で • 言語機能をガンガン足していく – 5章:複文、変数代入・参照 – 6章:if 文、while 文 – 7、8章:関数呼び出し、関数定義 – 9章:配列やハッシュの作成・参照・代入 MinRuby インタプリタの出来上がり – 執筆時に工夫したこと • 各章で一旦完結させる – 動作しない状態で 終わらない – 演習問題を入れる • 退屈にならないようにする – 実装対象の言語機能を考察する – 環境などの必要な拡張をしたり – each とか使ってない (ブロックがないので使えない)

Slide 23

Slide 23 text

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 行

Slide 24

Slide 24 text

目次 • 『Ruby でつくる Ruby』ダイジェスト – 概要 – インタプリタの構成と実装例 – ゴールと意義  今からここ • Ruby でつくる型付き Ruby – Ruby と型の概観 – 漸進的型付けのアイデアと実装例 • まとめ

Slide 25

Slide 25 text

できたもの • MinRuby で書かれた MinRuby インタプリタ  インタプリタ自身も MinRuby プログラム インタプリタがインタプリタ自身を実行できる! (ブートストラップ)

Slide 26

Slide 26 text

本書のゴール:ブートストラップ • 自分で書いたインタプリタで • 自分で書いた インタプリタ を 動かす ※インタプリタで ブートストラップとは 言わないかも

Slide 27

Slide 27 text

RubyでRubyインタプリタを書く意義 • 実用上は無意味 – 機械語で書かれていないインタプリタは役に立たない – 世の中、意味のないことのほうが面白い • インタプリタの原理と構成を理解できる – 教科書的な知識だけでなく実際に手を動かすのは重要 – ブートストラップに成功したときの達成感は半端ない • Ruby 言語自体を深く考察する土台になる – たとえば型とか

Slide 28

Slide 28 text

目次 • 『Ruby でつくる Ruby』ダイジェスト – 概要 – インタプリタの構成と実装例 – ゴールと意義 • Ruby でつくる型付き Ruby – Ruby と型の概観  今からここ – 漸進的型付けのアイデアと実装例 • まとめ

Slide 29

Slide 29 text

型とは • 変数の取りうる値の範囲を表現したもの – この呼び出しは 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)

Slide 30

Slide 30 text

型の目的 • 関数引数の想定を簡潔にドキュメント化したい – うざい • テストなしで自動的に『バグ』を見つけたい – テストで 1 時間待ちたくない sleep 3600 add("foo", 2) # 引数 x と y は Integer を受け付けます def add(x, y) x + y end

Slide 31

Slide 31 text

型で見つけたい『バグ』とは? • この関数に対しては – これにバグはない – ほぼ明らかにバグ – 動くけど目的外利用 def add_int(x, y) x + y end add_int(1, 2) add_int("foo", 2) add_int("foo", "bar")

Slide 32

Slide 32 text

型で見つけたい『バグ』とは? • この関数に対しては – これにバグはない – ほぼ明らかにバグ – OKかも?(ダックタイピング) – バグかどうかはプログラム単体では一意に決まらない – プログラマの意図に依存する def add(x, y) x + y end add(1, 2) add("foo", 2) add("foo", "bar")

Slide 33

Slide 33 text

ダックタイピングを許す型注釈の例 • 解決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

Slide 34

Slide 34 text

型のその他の問題 • 型注釈を書くのが面倒 • 型注釈のない既存資産が大量に存在する – これらもだいたい漸進的型付けで解決できる

Slide 35

Slide 35 text

目次 • 『Ruby でつくる Ruby』ダイジェスト – 概要 – インタプリタの構成と実装例 – ゴールと意義 • Ruby でつくる型付き Ruby – Ruby と型の概観 – 漸進的型付けのアイデアと実装例  今からここ • まとめ

Slide 36

Slide 36 text

漸進的型付け [Siek ‘06] のアイデア • 型ありと型なしの同居を認める – 型注釈があったら型チェックされる – 型注釈がなかったら型チェックしない • Ruby での使い方 – ダックタイピングしたいところには型注釈を書かない – 型注釈のない既存資産もとりあえずそのまま使える (気が向いたときに書き足してもよい) def add(x, y) x + y end def add_int(x:Integer, y:Integer) x + y end

Slide 37

Slide 37 text

漸進的型付けの実装イメージ • 型注釈がない変数は any 型とする • any 型が絡む演算の結果は any 型とする – すごく単純なアイデアだけど 2006 年頃にようやく登場 • 部分型(継承関係)を使った アプローチが研究の泥沼だった – 漸進的型付けは Python 3 の型ヒントや TypeScript の基盤でもある (つまり多分 Ruby でも使える?) J. G. Siek, et al. Gradual Typing for Functional Languages より引用

Slide 38

Slide 38 text

実装してみた、デモ 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") #=> 実行前に型エラー ※型注釈を含む構文解析は自作しました (本には未掲載、本で読みたい人はラムダノート社長の鹿野さんを煽ってください)

Slide 39

Slide 39 text

まとめ:型付き MinRuby • MinRuby を実験用環境として使う – クラスやブロックなど、難しいものがないので実験し やすい • 漸進的型付けの Ruby 実用化への課題 – クラスやブロックなど、難しいものにも対応しないと いけない • Python や TypeScript が行けたくらいだから多分できる? – 型推論の併用を考える • TypeScript は局所型推論みたいなことをやってる (関数の引数の方は推論しない)

Slide 40

Slide 40 text

目次 • 『Ruby でつくる Ruby』ダイジェスト – 概要 – インタプリタの構成と実装例 – ゴールと意義 • Ruby でつくる型付き Ruby – Ruby と型の概観 – 漸進的型付けのアイデアと実装例 • まとめ  今からここ

Slide 41

Slide 41 text

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