ピュアSwiftコード100行でLispインタープリター作ろうとしたけど185行になったよ

English version of this page: knj4484: Lisp interpreter implemented in 185 lines of Swift

背景

  1. 尊敬するプログラマーの一人、 @himara2 さんの「SwiftでTiqav APIを叩くビューワアプリを100行でつくったよ - Think Big Act Local」を読みました
  2. かつてRubyLispを作った時は100行要らなかったことを思い出しました
  3. ひょっとしてSwiftでも100行以内でLispを書けるのかなと思っいました

このLispインタープリターで出来ること

(+ 0 (* 11 22) (- 333 30 3))
(first (1 2 3))
(let (a 2 b 3 c 4) (+ a b c))
(if (< 1 3) 2 3)
(map (list 1 2) (\ (x) (* 2 x)))
(defun calc (a b) (+ a b))
(calc 4 8)
  • 整数の算術演算 + - * / %
  • リスト操作 list quote first rest
  • ローカル変数 let
  • 比較演算 < > =
  • if
  • プロシージャー \
  • map
  • 関数定義 defun

実装方針

少ない行で書くことが目的なので、以下の方針で作ることにしました

  • 簡単かつ最小限の機能のみ実装
  • エラー処理はしない
  • パフォーマンスは気にしない
  • 関数型プログラミングっぽいコーディングスタイル

使い方

※エラー処理を一切していませんので、実行できないLispプログラムを実行しようとすると、アプリが固まります。再起動して下さい

サンプルプログラム

フィボナッチ数列の計算もちゃんとできます

(defun f (x) (if (= x 0) 1 (if (= x 1) 1 (+ (f (- x 1)) (f (- x 2))))))
(map (list 1 2 3 4 5 6 7 8 9 10) (\ (x) (f x)))

結論

knj4484$ wc -l Sweme2/{Evaluator,Expression}.swift
     153 Sweme2/Evaluator.swift
      32 Sweme2/Expression.swift
     185 total

少なくとも現時点の自分の技術力では、100行以内のSwiftコードLispインタープリターを作ることは無理でしたが、Swiftはコードを非常に簡潔に書くことが出来る言語だと分かりました。

考察

Swiftの良いところ

  • case
    • ラベルで使うletがとても便利
    • breakが要らないのはよい
  • 複数の値を返すときに使うタプルが便利
    • タプルがないとわざわざそれらの値を含むクラス定義が必要になる
  • ifでoptional bindingが出来るのがよい
  • クロージャーを書く時の記法($0~$9、return省略)でスッキリ書ける

Swiftのいまいちなところ

※まだβ版なので、正式リリースでは改善されているかもしれません

  • 文字列の処理が貧弱(このせいでTokenizerの実装がとても大変だった)
    • n番目の文字を取り出すのも一苦労
  • defaultが必須なのはやりすぎ
  • クロージャーでreturn省けるのなら、そもそも普通の関数でもreturn省けていいんじゃないか
  • ifの条件ではoptional bindingができるのだから、caseラベルでも出来て欲しかった
    • case let x = f(): x
  • [1,2,3][1..<2]の型が[Int]じゃなくてSliceなのがきつい
    • これはエラーメッセージも分かりにくかったし、ハマった
  • 文字列のインデックスが演算できなくてadvance関数を使わなければいけない

今後の展望

  • マクロ展開機能を実装する
  • 文字列と小数の演算を実装する

参考