Lisp Reader Macro Advent Calendar 2012の8日目の記事です。

今日は、みなさんお待ちかねのArc。はたして、Arcでリーダーマクロとは?

はい、そんなのない。少なくとも、Paul Grahamが公開しているオリジナルのArcには、リーダーマクロはまだない。ここで話を終わらせてもいいのだが、とりあえず、Arcのread関数の定義でも見てみることにする。

read関数は、arc.arcで定義されているので、該当部分を抜き出す。

(def readstring1 (s (o eof nil)) (w/instring i s (read i eof)))

(def read ((o x (stdin)) (o eof nil))
  (if (isa x 'string) (readstring1 x eof) (sread x eof)))

えーと、最初に、stringかinput portかの判定をしている。おっと、実際の処理はsread関数みたいだね。

それでは、sread関数の定義を見てみる。ac.scmの該当部分。うーむ、これはSchemeか。ご存知のとおり、ArcはRacketで実装されている。

(xdef sread (lambda (p eof)
               (let ((expr (read p)))
                 (if (eof-object? expr) eof expr))))

xdefはRacketからArcのネームスペースにバインドするためのマクロ。ああ、結局、Racketのread関数に丸投げしてる。Racketのread関数は終端まで行ったらeof-objectが返るので、それをArcのためにラップしているだけだ。ちなみにコメントによると、sreadはscheme readの略のようなので、そのままだね。

以上、Arcのread関数の定義を見てみた。要するにRacketのread関数に丸投げなので、Racketのリードテーブルを変更してやれば、Arcでもリーダーマクロっぽいことがやれそうだ。

というわけで、Racketのリードテーブルのドキュメントを読んでみる。

意味がわからない。Racketも英語も苦手。むしろ、読む気がない。

どうしようもないので、ArcからRacketのリードテーブルを変更するライブラリを公開している人がいるので、それをつかうことにしよう。Arcならわかる。こういうときに、ユーザーの多い言語は便利ですね(遠い目)。ちなみに、このライブラリを少し修正したものを、arc-mgにも取り込んでいる。

準備が整ったので、例としてハッシュテーブルをClojureっぽく{ ... }で書けるようにしてみよう。

(def parse-table-items (port (o acc '(obj)))
  (scheme.skip-whitespace port)
  (if (is peekc.port #\})
      (do readc.port (ar-nil-terminate:rev acc))
      (with (k read.port v read.port)
        (parse-table-items port (join (list v k) acc)))))

(extend-readtable #\{ parse-table-items)
(extend ac-literal (x) (errsafe:isa x 'table) scheme-t)

簡単だね。

arc> {x "foo" y "bar"}
#hash((y . "bar") (x . "foo"))

ばっちり。

普段コードを書いていても、通常のマクロで十分なことが多く、リーダーマクロの最適な使い所に出会うことは少ないかもしれない。でも、自分の道具箱の中に忍ばせておくと、いざというときに表現の幅が広がっていいのではないだろうか。