with-slots
Common Lisp にある with-slots を Gauche でも使ってみる。次のクラスがあったとき、
(define-class <person> () ((name :init-keyword :name) (gender :init-keyword :gender) (age :init-keyword :age)))
こんなふうに使えるようにする。
;; Example-1 (let ((hoge (make <person> :name "Hoge" :gender 'male :age 20))) (with-slots (name gender age) hoge (format #t "~a: ~a ~d~%" name gender age)))
まず define-syntax を使ってマクロを書いてみた。
(define-syntax with-slots (syntax-rules () ((_ () obj body ...) (begin body ...)) ((_ (slot1 slot2 ...) obj body ...) (let ((slot1 (slot-ref obj 'slot1))) (with-slots (slot2 ...) obj body ...)))))
Example-1 を評価した限り、うまく動いているように見える。しかし次のような場合はどうか。
;; Example-2 (with-slots (name gender age) (make <person> :name "Hoge" :gender 'male :age 20) (format #t "~a: ~a ~d~%" name gender age))
これだとスロットが展開されるたびに
(define-method initialize ((p <person>) initargs) (next-method) (format #t "[~a] initialized.~%" (slot-ref p 'name)))
with-slots で指定した数だけ initialize が呼び出されるのがわかる。
たぶん、define-syntax でもこういう場合の回避方法はあるんだろうが、今のところ不明なので define-macro でマクロを書くことにした(追記: shiro さんのコメント参照)。こちらは Common Lisp のマクロの書き方がそのまま参考になる。
(define-macro (with-slots slots obj . body) (let ((gobj (gensym))) `(let ((,gobj ,obj)) (let ,(map (lambda (slot) `(,slot (slot-ref ,gobj ',slot))) slots) ,@body))))
今度は最初に一回だけ obj を評価して展開するので Example-2 でも問題ない。
Common Lisp の with-slots は束縛するシンボルも指定できる。
(with-slots ((name1 name) (age1 age)) person1 (with-slots ((name2 name) (age2 age)) person2 ;; ... ))
でも今回は省略。