2010年4月12日月曜日

alet

LET OVER LAMBDA Edition 1.0ではaletと言うアナフォリックマクロも紹介されています。定義は以下の通り。

CL-USER> (defmacro alet (letargs &body body)
`(let ((this) ,@letargs)
(setq this ,@(last body))
,@(butlast body)
(lambda (&rest params)
(apply this params))))
ALET
CL-USER>

aletの動作例はalambdaと組み合わされたなかなか複雑なものが紹介されています。

CL-USER> (alet ((acc 0))
(alambda (n)
(if (eq n 'invert)
(setq this
(lambda (n)
(if (eq n 'invert)
(setq this #'self)
(decf acc n))))
(incf acc n))))
#<CLOSURE (LAMBDA (&REST PARAMS)) {B40D5D5}>
CL-USER>

これはなかなか凄い例です(笑)。aletで指示する代名詞thisの中身をsetqで置き換えてる。
実行例は次の通りです。

CL-USER> (setf (symbol-function 'alet-test) *)
#<CLOSURE (LAMBDA (&REST PARAMS)) {BC6900D}>
CL-USER> (alet-test 10)
10
CL-USER>

シンボルinvertを渡すとalet-testの挙動が変わります。

CL-USER> (alet-test 'invert)
#<CLOSURE (LAMBDA (N)) {B02F49D}>
CL-USER> (alet-test 3)
7
CL-USER>

つまり、alet内のthisの中身が置き換えられています。

CL-USER> (alet-test 'invert)
#<CLOSURE (LABELS SELF) {BC6D605}>
CL-USER> (alet-test 5)
12
CL-USER>

面白いですね。代名詞を使って丸ごと関数の挙動を変えるとは……。

では、R5RS Schemeで同様のマクロを記述するにはどうするか?
前回見た通り、衛生的マクロではアナフォリックマクロは直接書けません。従って、ここでも外部から束縛すべきシンボルを与えるPseudoなアナフォリックマクロに改造してみます。

(define-syntax alet
(syntax-rules ()
((_ this ((letarg letvar) ...) body ... last)
(let ((this #f) (letarg letvar) ...)
(set! this last)
body ...
(lambda params
(apply this params))))))

割に個人的な感想では、LET OVER LAMBDA Edition 1.0ではCLのletらしい乱暴な使い方をしています。
CLの場合、letは束縛されるべきデータ側を省略しても問題ないようにデザインされています。この場合thisがそうなんですけど、Schemeではそれは許されません。従って、仮に与えるデータとして#fを束縛しておきます。
もう一つはSchemeヴァージョンのlambda式の引数が括弧無しの場合、与えられた引数はリストになります。
ではSchemeインタプリタで動作を確認してみます。

> (define alet-test (alet this ((acc 0))
(alambda self (n)
(cond ((eq? n 'invert)
(set! this
(lambda (n)
(cond ((eq? n 'invert)
(set! this self))
(else
(set! acc (- acc n))
acc)))))
(else
(set! acc (+ acc n))
acc)))))
> (alet-test 10)
10
> (alet-test 'invert)
> (alet-test 3)
7
> (alet-test 'invert)
> (alet-test 5)
12
>

上手く動いていますね。

0 件のコメント:

コメントを投稿