Posted 2017-12-13 11:52:38 GMT

Lisp Advent Calendar 2017 十三目です。

空きがあったのでネタで埋めようと思い書きました。

十三目書いてたのに!という方は、すいませんが、まだ空きがあるので他の日を埋めてください。

マクロが禁止されたらどうなるの

良くも悪くも誤解が多いLispマクロ。

Lispマクロに心酔するあまり過大評価する人もいれば、過大評価する人をみて過小評価に転ずる人もいる始末ですが、基本的にはコードを生成するだけの機能です。

そんなマクロですが、Common Lispで仮に禁止されたらどうやって生きていったら良いのか考えてみました。

(ちなみに話を簡単にするためにローカルマクロのことは考えないことにします。)

defmacro は何をしているの

Common Lispの defmacro で定義するものは、リストを引数にして、リストを返すという関数です。

しかし、評価の前に再帰的にマクロを展開するフェイズがあり、そこで展開関数が実行されるので、まるで関数評価のような感じで使うことができます。

例えば、下記のようなコードで loop の展開関数だけ実行することも可能です。

funcall macro-function ' loop loop :for i :from 0 :repeat 10 :collect i 10 nil

関数だけで defmacro のようなことをしてみる

例として dotimes のようなものを考えてみましょう

'(dotimes (i 10) (princ i))

のようなリストを

prog ( ( #:|limit17589| 10 ) ( i 0 ) ) "=>" cond ( <= #:|limit17589| i ) return let nil nil progn princ i incf i go "=>"

のようなリストに変形すれば良いので、

( form &optional env ) declare ignore env destructuring-bind ( _ ( var limit &optional result ) &body body ) form declare ignore _ let gensym "limit" limvar gensym "=>" tag prog ( ( ,limvar ,limit ) ( ,var 0 ) ) ,tag cond ( <= ,limvar ,var ) return let ,var nil ,result progn ,@body incf ,var go ,tag ,tag form defun mydotimes

のような関数を書けるでしょう。

(まあ、結局の所マクロを書く作法が身に付いていないと、こういう関数も書けないのですがそれは一旦忘れましょう)

これで、下記のように書けます。

with-output-to-string ( out ) declare special out eval ( i 3 ) , ( j 3 ) princ i out princ j out mydotimes mydotimes ' mydotimes mydotimes ` → "000102101112202122"

やはりマクロ展開と実行コードを混ぜて書かないといけないので、ごちゃごちゃしてしまいます。

別個にマクロ展開関数を用意して、オペレーターが定義したマクロかどうかを確認しつつ展開するようにすれば、

with-output-to-string ( out ) declare special out ( i 3 ) ( j 3 ) princ i out princ j out mydotimes mydotimes mexpand ` → "000102101112202122"

位には圧縮できるかもしれません。

もうちょっと綺麗にできないか

とりあえずは、安直に簡単に見た目を変える方向で、リーダーマクロを使ってごちゃごちゃを隠してみましょう。

見た目がごちゃごちゃしているだけではなく、上記では、変数の結合も実行時にしているので、変数をダイナミック変数に指定していたりします。この辺りもリーダーマクロで読み取り時に展開してしまえば解決です。

なお、リーダーマクロも禁止ならファイルを2パスで処理する等々しかないですね。

set-syntax-from-char #\] #\) set-macro-character #\[ lambda ( s c ) declare ignore c let read-delimited-list #\] s T form funcall car form form

with-output-to-string ( out ) [mydotimes ( i 3 ) [mydotimes ( j 3 ) princ i out princ j out [mydotimes[mydotimes]] → "000102101112202122"

結論

結局の所、

コードがデータ

評価器に渡るコードを変形するフックがユーザーに開放されている

の2点が言語に備わっていれば、Lispマクロのような機能と使い勝手は実現可能だということが分かるでしょうか。

特にLispには限らない筈ですが、使い勝手を含めて真面目に活用が考えられてきた、また実績があるのは、ほぼLisp系言語のみ、というのが現状だと思います。

誕生当初は、LispもM式→S式の変換をして実行するものと考えられていたLispですが、S式というデータの世界にLispプログラマが飛び込んだことが偉大だったのかもしれません。

■

