Clojure
ClojureDay 4

Scheme in Clojure

More than 3 years have passed since last update.

初めに

この記事は 「つくって学ぶプログラミング言語 RubyによるScheme処理系の実装」(以降 Scheme in Ruby)をClojureで試した際のメモです.

RubyでもClojureでも基本部分は共通ですが, 別の言語でとなると異なる部分もあります.
そこで, Scheme in Ruby の中で自分が引っかかったこと, RubyからClojureに変更する上でのことをまとめていきます.

前提として

RnRS準拠というような本格的な実装を目指したものではなく, あくまでも作って学ぶためのごく簡単なものです.
文法も自由ですし, そもそも字句解析(?)を行いません.

仮に

(+ 2 3)

というSchemeの式があったとして, 本来なら

"(+ 2 3)"  ;;java.lang.String

という文字列をパースして

[+ 2 3]  ;;clojure.lang.PersistentVector

というClojureが解釈できるデータに落としこむ手順が必要になります

ところがこの処理の実装はそれだけでかなりの労力が必要となる上に, Schemeの実装そのものとは別分野になってしまいます.
そこで, どう下位の言語で処理するか, という部分に焦点を絞って, 解釈したデータを実行する部分のみを実装します.
つまり, Clojureのデータ構造で表現された言語を評価する関数をClojureで書くだけです.

長い、3行で

省エネ仕様
学習には最適
関数書くだけ

Clojureでのモデル

あくまでも一例です. もっと綺麗な方法はたくさんあると思います.

構文

Rubyでは配列とシンボルで表現されていますが, Clojureではベクタとキーワードで表現します.
リストでもいいのですが, Clojureと別言語であることを明確にするためあえてベクタにします.

[:+ 2 3] 
[:+ :x :y] 
[[:lambda [:x  :y] [:+ :x :y]] 3 2]
[:let [[:fact [:lambda [:n] [:if [:< :n 1] 1 [:* :n [:fact [:- :n 1]]]]]]] [:fact 5]]

環境モデル

マップのベクタで表現します.
マップではなくマップのベクタなのは, lambda等を実装する上で複数の環境を考える必要があるためです.

[{:x 5 :y 7}, {}, ...]

実行モデル

(-eval Expression Environment)

(-eval [:+ 2 3] [{}])
=> 5
(-eval [:+ :x :y] [{:x 5 :y 7}])
=> 12
(-eval [[:lambda [:x  :y] [:+ :x :y]] 3 2] [{:x 5 :y 7}])
=> 5
(-eval [:let [[:fact [:lambda [:n] [:if [:< :n 1] 1 [:* :n [:fact [:- :n 1]]]]]]]
        [:fact 5]] [{}])
=> 120

ソースコード

GitHub
Scheme in Ruby の第3章までClojureで書いたソースコードです.
コメント込みで100〜200行くらいになりました.

最後に

ここまでお付き合いいただきありがとうございました.
何かあったらコメントしていただけると幸いです.