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

【20190319 KotlinLT】Kotlinで雑に作るLispインタープリタ

【20190319 KotlinLT】Kotlinで雑に作るLispインタープリタ

【とらのあな主催】オタクがKotlinを追うライトニングトークイベントにて使用した発表使用になります。
Kotlinの勉強がてらLispの一部機能を実装したインタープリタを作ってみたので発表しました。

More Decks by 虎の穴ラボ株式会社

Other Decks in Technology

Transcript

  1. 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 Copyright © 2019 Toranoana Inc. All Rights Reserved.

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

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

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

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

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

    Lispの軽い説明 Lispのよく聞くイメージ • かっこが多い(+ 1 (- 9 ( * (/ 2 5) (+ 1 29)))) • 変態が使う言語 • エイリアン 7
  8. 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 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
  9. 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 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<String> = line.replace(LEFT_BRACES, "$SPACE$LEFT_BRACES$SPACE") .replace(RIGHT_BRACES, "$SPACE$RIGHT_BRACES$SPACE") .split(SPACE).map { it.trim() }.filterNot { it.isBlank() }
  10. 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 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))")) } } }
  11. 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 Copyright © 2019 Toranoana Inc. All Rights Reserved.

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

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

    構文解析(ロジック) tailrec fun read(tokens: List<String>, cell: Cell? = null, braceStack: List<Cell> = listOf()): Cell? { // 中身省略 } 13 末尾再帰するためのキーワード
  14. 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 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; } // 以降も省略 } } 再帰がループに置き換えられている!!
  15. 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 Copyright © 2019 Toranoana Inc. All Rights Reserved.

    15 !!WARNING!! ここから先は以下の要素が含まれます。 • Any型の乱用 • なんの保証もないキャスト • null安全の崩壊
  16. 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 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<Any>? -> args!!.fold(0) { acc, number -> acc + (number as Int) } } // 略 env.put(">") { args: List<Any>? -> (1..(args!!.size - 1)).map { Pair(args[it - 1], args[it]) }.all { (it.first as Int) > (it.second as Int) } } // 略 return env }
  17. 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 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 } }
  18. 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 虎の穴 Copyright © 2019 Toranoana Inc. All Rights Reserved.

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

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