#:g1: ()の再定義

Posted 2012-12-23 17:10:00 GMT

もうネタがないので、禁断の()の再定義について自分が試したことを紹介してみたいと思います。

()を再定義したい

そもそも()を再定義したいことなんてあるのか、という話になりますが、私の経験上では一例しかありません。
その一例ですが、TAO/ELISという日本が誇る自由過ぎるLisp方言で『!』という文字が多義的に使われていたのをどうにか再現してみたい、と考えた末、()を再定義するのが一番簡単だ、という結論になり再定義してみることとなりました。
参照:

TAOの『!』の扱いについては、ブログでも以前にも何度か考察していましたが(TAOの!再び)、リーダーマクロを定義という観点から、少し詳しく解説してみたいと思います。

TAO/ELISでの『!』

まず、TAO/ELISで『!』という文字がどのように使われているかを説明すると、

  1. (!x 42) というsetfのような代入式
  2. (!!foo x y !z) という自己代入式
    • (!!foo x y !z) ≡ (setq z (foo x y z))
  3. (!!foo x y !z) という自己代入式での代入先のシンボルを示す印(ここでは!z)
  4. (! x y z) という論理プログラミングでのバックトラックするor(『!』の後に空白あり)
  5. (! x y ! z) という論理プログラミングでのカット記号
  6. (cdr! ...)・(cons! ...)・(append! ..)という関数名
となっています。
参照:
まず、(!x 42)のようなものを(setf x 42)のように展開するには安直に『!』をsetfに置き換えれば良いのですが、そうすると(! ...)が上手く行きません。
それだったら、『!』の後の一文字を見て分岐すれば良いじゃないかということになります。
これで、(!x ...)・(!!foo ...)・(! ...)はOKです。non-terminating-pをTにすれば、(cdr\!)ではなく、(cdr! ...)と書けます。
これで解決かというところなのですが、文字を展開するだけのリーダーマクロだけに、自己代入式の(!!foo x y !z)の!zと、(!z 42)の!zの区別が付かないので
(let ((x 1)(y 2) (z 3))
  (!!list (!x 100) (!y 200) (!z (list !y)))
  y)
;=>  (100 200 (200))
のようなものは、
(let ((x 1) (y 2) (z 3))
   (selfassign list
               (setf x 100)
               (setf y 200)
               (setf z (list setf y)))  ;!!!
   y)
という風に展開されてしまいます。また、論理プログラミングのカット記号も
(! x y ! z)
のようなものは、
(logic-or x y setf z)
と展開されてしまいます。
これらの根本的な原因は何かと考えると、!xが関数呼び出しの位置で呼ばれたのか、引数の位置で呼ばれたのかがリーダーには分からないということにあります。
これを読み取り時に判定させるには、『(』の読み取りで次の文字を読んで分岐させる位しか自分には思い付きませんでした。
『(』を分岐の基点にして、関数位置だけを処理することにすれば、引数の位置のものは、そのまま未処理で次に渡せるので、関数位置と引数位置の区別は可能ということになります。
TAOでは、豊富なタグを使って『(!』という種類の特殊な括弧(オブジェクト)を扱うようになっているようなので、処理方式としては、ある意味近いかなというところでもあります。

練習問題 24. 上記のようなTAO/ELISの『!』を作ってみましょう

リーダーマクロの話が一段落したので、全く話の流れとは関係がないのですが、折角TAOの『!』について触れたので、書き残しておきます。
TAOでは、当初代入や、自己代入式は、(!x 42)・(!!foo !x y z)ではなく、(:x 42)・(::foo !x z)だったようで、途中から『!』に変更になったようです。何故変更になったのかは、ELIS復活祭での竹内先生のお話でもはっきりした由来が定かではなかったようですが、Common Lispがキーワードで『:』を使ったこととの競合回避ではないかとのことです。

まとめ

以上、今回は、()の再定義について書いてみました。
()はLispにとって最も重要といっても良い文字だと思いますが、これの再定義に失敗した場合、何から何までおかしくなってしまうことが多いかと思います。
開発に於てはリードテーブルを切り換える仕組みは必須ですが、何か変だなと思ったら、潔く処理系を再起動しましょう…。

comments powered by Disqus