Displaying posts tagged with

“『実践Common Lisp』”

多重バッククォートとアンクォート

頭が混乱したときのためにメモ。

同等なlistによる表現に書き直す

内側のバッククォートから順に書き直していく。例えば

``(,,n ,,g)

に対するlistによる表現は

`(list ,n ,g)

となる。同様に

`(a ,b `(,,a ,b))

なら

`(a ,b (list ,a b))

である。

評価した結果を求める

右側のアンクォートから評価していく。例えば a=c, b=1 という束縛が存在する環境で

``(,,a ,,b)

を評価すると

`(,c ,1)

になる。また’によるクォートと併用する場合も右から順に考えれば分かりやすい。例えば

`(a ,b `(,',a ,b))

を評価すれば

(a 1 `(c ,b))

を得る(クォートとアンクォートは打ち消し合う)。

『実践Common Lisp』8章 once-onlyマクロ

自分で考えて書いてみた。本に載っている例とは少しだけロジックが異なるが、同様に動作する。

(defmacro once-only ((&rest names) &body body)
  (let ((gensyms (loop for n in names collect (gensym))))
    `(let (,@(loop for n in names for g in gensyms collect `(,g ,n)))
       (let (,@(loop for n in names collect `(,n (gensym))))
	 `(let (,,@(loop for n in names for g in gensyms collect ``(,,n ,,g)))
	    ,,@body)))))

ところでこの中の (,@(loop …)) となっている部分を、次のように ,(loop …) に置き換えると、

(defmacro once-only ((&rest names) &body body)
  (let ((gensyms (loop for n in names collect (gensym))))
    `(let ,(loop for n in names for g in gensyms collect `(,g ,n))
       (let ,(loop for n in names collect `(,n (gensym)))
	 `(let ,,(loop for n in names for g in gensyms collect ``(,,n ,,g))
	    ,,@body)))))

なぜか動作しなくなる(do-primesのコンパイルでエラー)。理由がよく分からない。with-gensyms は後者の形で動いているのに…。

『実践Common Lisp』3.8のwhereを関数で実装する

マクロで実装された where を他の言語的な発想で、つまり関数(クロージャ)を使って実装したらどうなるか、考えてみた。

(defun make-comparison-fun (field value)
  (lambda (cd) (equal (getf cd field) value)))
 
(defun all (fun lst)
  (cond ((not lst) t)
	((not (funcall fun (car lst))) nil)
	(t (all fun (cdr lst)))))
 
(defun where-fun (&rest clauses)
  #'(lambda (cd)
      (all #'(lambda (comp-fun) (funcall comp-fun cd))
	   (loop while clauses
	      collecting (make-comparison-fun (pop clauses) (pop clauses))))))

こうして書き比べてみると、関数という単位でしか処理を分割できないのはとても不自由で、不自然だとすら感じてしまう(そう感じてしまう自分に驚く)。make-comparison-fun の本当の意図は「今すぐには評価しない、後で評価するためのコードブロックを作る」ことであって、関数という形式にはあまり意味がない。all や where-fun の中で funcall を呼ぶのも煩わしい。

実際、普通は上のようなコードは書かないだろう。不自然で不自由で、抽象化過剰のように感じられるからだ。この程度の内容ならば、一切の抽象化を放棄して単純なループで書いてしまう気がする。

function where() {
    var fields = arguments;
    return function(cd) {
	for(var i=0; i<fields.length; i+=2) {
	    if(cd[fields[i]] != fields[i+1])
		return false;
	}
	return true;
    }
}

しかしこの手の妥協を繰り返していると、やがてテストするのがしんどくなってくる。どこまで抽象化すべきか、どこで抽象化を諦めるか、特に再利用可能なライブラリを作っているときにはかなり悩ましい問題となる。

Lispではそういう妥協は不要ということだろうか。今後の内容に期待しよう。

実践 Common Lisp 3.8

少しずつだが『実践Common Lisp』を読み始めた。3章の where の例で早くも感動。Lispのマクロはとにかく凄いのだと、著者がくどいくらいに主張する理由が分かってきた。確かにこれは凄い。

練習のために update のマクロ版も書いてみた。後の章で出てくるのかも知れないが。

(defun make-update-expr (field value)
  `(setf (getf cd ,field) ,value))
 
(defmacro update (selector-fn &rest clauses)
  `(setf *db*
	 (mapcar
	  #'(lambda (cd)
	      (when (funcall ,selector-fn cd)
		,@(make-expr-list #'make-update-expr clauses))
	      cd)
	  *db*)))

make-expr-list は make-comparison-list を一般化したもので、次の通り。

(defun make-expr-list (make-expr-fn fields)
  (loop while fields
       collecting (funcall make-expr-fn (pop fields) (pop fields))))