Slide 1

Slide 1 text

虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 Copyright © 2019 Toranoana Inc. All Rights Reserved. Kotlinで雑に作るLispインタープリタ 虎の穴ラボ 藤原 佳顕 1 2019.03.19 オタクがKotlinを追うライトニングトークイベント

Slide 2

Slide 2 text

虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 Copyright © 2019 Toranoana Inc. All Rights Reserved. アジェンダ • 自己紹介 • はじめに • 動機 • Lispの軽い説明 • 字句解析編 • 構文解析編 • eval編 2

Slide 3

Slide 3 text

虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 Copyright © 2019 Toranoana Inc. All Rights Reserved. 自己紹介 (hash-map :name "藤原 佳顕(28)" :desc "にわかLisperかつフロントエンドマン" :work "Fantiaでフロント周りを中心に色々" :like (hash-map :game '("レイストーム" "ダライアス外伝") :anime '("スクライド" "Show By Rock") :tech '("Clojure" "TypeScript"))) 3

Slide 4

Slide 4 text

虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 Copyright © 2019 Toranoana Inc. All Rights Reserved. はじめに • Kotlinの勉強がてら簡単なLispを(雑に) 実装してみているので紹介します。 • 本LTには以下の要素が含まれます – Any型 – null安全の破壊 – 安全じゃないキャスト 4

Slide 5

Slide 5 text

虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 Copyright © 2019 Toranoana Inc. All Rights Reserved. • 言語を勉強するときはなにか作りたい! – Webアプリはフレームワークも知らないとつらい・・・ – ほぼ素のKotlinで書けるCUIアプリにしよう! • Lisp方言をちょっと触っていたこともあり実装が簡単そうなLispイン タープリターの実装を通してKotlinの文法などを知ろう!というモチ ベーション 5 動機

Slide 6

Slide 6 text

虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 Copyright © 2019 Toranoana Inc. All Rights Reserved. Lispの軽い説明 突然ですがみなさん・・・ Lispってご存知でしょうか? 6

Slide 7

Slide 7 text

虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 Copyright © 2019 Toranoana Inc. All Rights Reserved. Lispの軽い説明 Lispのよく聞くイメージ • かっこが多い(+ 1 (- 9 ( * (/ 2 5) (+ 1 29)))) • 変態が使う言語 • エイリアン 7

Slide 8

Slide 8 text

虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 Copyright © 2019 Toranoana Inc. All Rights Reserved. Lispの軽い説明 Lispは(拡張がなければ)文法が簡単!! • 基本的にはカッコと予約語だけ(S式) • 今回は時間と紙面の都合で以下の中でも四則演算と比較演算子、ifが実行で きるまで実装します (+ 1 (- 100 99)) ;;=> 四則演算 1 + (100 - 99) = 2 (= 1 1) ;;=> true (> 1 1) ;;=> false (< 1 1) ;;=> false (if (= 1 1) "True" "False") ;; 特殊形式ifの例 8

Slide 9

Slide 9 text

虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 Copyright © 2019 Toranoana Inc. All Rights Reserved. 字句解析 今回は雑に作るので、手抜きします。 ※“(+ 1 1)” → [“(“, “+”, “1”, “1”, “)”]に分解しているだけ 9 const val LEFT_BRACES = "(" const val RIGHT_BRACES = ")" const val SPACE = " " // 文字列を空白でパースしてトークンのリストに変換 fun tokenize(line: String): List = line.replace(LEFT_BRACES, "$SPACE$LEFT_BRACES$SPACE") .replace(RIGHT_BRACES, "$SPACE$RIGHT_BRACES$SPACE") .split(SPACE).map { it.trim() }.filterNot { it.isBlank() }

Slide 10

Slide 10 text

虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 Copyright © 2019 Toranoana Inc. All Rights Reserved. 字句解析(テスト) 10 describe("tokenizeを呼んだ時") { it("文字列が入力された場合文字列のリストが生成される") { assertEquals(listOf("(", "+", "1", "1", ")"), tokenize("(+ 1 1)")) } it("入れ子のカッコの場合") { assertEquals(listOf("(", "+", "1", "(", "*", "3", "10", ")", ")"), tokenize("(+ 1 (* 3 10))")) } context("S式として不正な場合でもトークン化は正常に終わること") { it("左括弧が無い") { assertEquals(listOf("+", "1", "1", ")"), tokenize("+ 1 1)")) } it("右括弧が無い") { assertEquals(listOf("(", "+", "1", "1"), tokenize("(+ 1 1")) } it("カッコの対応が不正") { assertEquals(listOf("(", "+", "1", "*", "3", "10", ")", ")"), tokenize("(+ 1 * 3 10))")) } } }

Slide 11

Slide 11 text

虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 Copyright © 2019 Toranoana Inc. All Rights Reserved. 構文解析 Lispのプログラムの形をよく見ると・・・ (+ 1 (- 100 99)) プログラム自体が再帰構造になっている!! ⇛しかし…Javaは末尾再帰最適化してくれないらしい 11 外側の括弧の演算 内側の括弧の演算

Slide 12

Slide 12 text

虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 Copyright © 2019 Toranoana Inc. All Rights Reserved. 構文解析(データ構造 データ構造自体も再帰構造とします 12 class Cell(val parent: Atom, val children: MutableList) {} 関数とか特殊形式とかを表したもの 引数リストとか。型を自分自身に することで括弧の入れ子対応を考 えない 出力イメージ: [“+”, 1, [“-”, 100 99]] parent parent children children

Slide 13

Slide 13 text

虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 Copyright © 2019 Toranoana Inc. All Rights Reserved. 構文解析(ロジック) tailrec fun read(tokens: List, cell: Cell? = null, braceStack: List = listOf()): Cell? { // 中身省略 } 13 末尾再帰するためのキーワード

Slide 14

Slide 14 text

虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 Copyright © 2019 Toranoana Inc. All Rights Reserved. 構文解析(ロジック) 14 DECOMPILEしてみた(Kotlin→バイトコード→Java) @Nullable public static final Cell read(@NotNull List tokens, @Nullable Cell cell, @NotNull List braceStack) { while(true) { // 中身省略 if (tokens.isEmpty()) { // 全トークンが処理完了したら終わり Collection var19 = (Collection)braceStack; if (!var19.isEmpty()) { throw (Throwable)(new BraceError("( is not found")); } return cell; } // 以降も省略 } } 再帰がループに置き換えられている!!

Slide 15

Slide 15 text

虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 Copyright © 2019 Toranoana Inc. All Rights Reserved. 15 !!WARNING!! ここから先は以下の要素が含まれます。 ● Any型の乱用 ● なんの保証もないキャスト ● null安全の崩壊

Slide 16

Slide 16 text

虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 Copyright © 2019 Toranoana Inc. All Rights Reserved. eval編(準備) 16 構文解析はできたので次は評価をしよう!まずは組み込み関数から // TODO: lambda.refrect()メソッドから生成される KFunctionとcallメソッドを使いたかった // が、KFunction自体がexperimental fun initEnv(): Env { //Envの実体はKeyが文字でValueがラムダ式(Anyにしちゃってますが )のHashMap val env = Env() env.put("+") { args: List? -> args!!.fold(0) { acc, number -> acc + (number as Int) } } // 略 env.put(">") { args: List? -> (1..(args!!.size - 1)).map { Pair(args[it - 1], args[it]) }.all { (it.first as Int) > (it.second as Int) } } // 略 return env }

Slide 17

Slide 17 text

虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 Copyright © 2019 Toranoana Inc. All Rights Reserved. eval編(実行) 17 Cellクラスのevalメソッドを呼びます。 fun eval(env: Env): Any? { return when (parent) { // 各シンボルに対して自身が持つevalメソッドを呼び出していく is Symbol -> parent.eval(env)?.invoke( // 引数の評価はchildrenのevalを呼び出す。(実質)再帰になっている children.map {elem -> elem.eval(env)!!} ) is If -> parent.eval(env)?.invoke(children) is Num -> parent.eval() is Str -> parent.eval() else -> null } }

Slide 18

Slide 18 text

虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 Copyright © 2019 Toranoana Inc. All Rights Reserved. デモ 18 (REPL起動)

Slide 19

Slide 19 text

虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 Copyright © 2019 Toranoana Inc. All Rights Reserved. まとめ • KotlinでLispの各種演算とifを実装することができました • 感じたメリット – 再帰処理などはJavaに比べて書きやすい – 関数についてもラムダ式で直感的に書ける • 今後:関数定義あたりまではやりたいと思っています • 型をあえてつけてほしくない場合の処理がイマイチ – sealedクラスとか使いこなせば・・・ • KFunctionはよ・・・ 19