with-slots (2)

引き続き、define-syntax を使った with-slots マクロの定義について。昨日の shiro さんのコメントを受けて修正しつつ、Common Lisp の with-slots の仕様に近づくように修正。以下のサンプルコードを用意。

(define-class <person> ()
  ((name :init-keyword :name)
   (gender :init-keyword :gender)
   (age :init-keyword :age)))

(define-method initialize ((p <person>) initargs)
  (next-method)
  (format #t "[~a] is born.~%" (slot-ref p 'name)))

(define (example-1)
  (with-slots (name gender age)
      (make <person> :name "Hoge" :gender 'male :age 20)
    (format #t "~a ~a ~d~%" name gender age)))

(define (example-2)
  (with-slots ((n1 name) (g1 gender))
      (make <person> :name "Hoge" :gender 'male)
    (with-slots ((n2 name) (g2 gender))
        (make <person> :name "Fuga" :gender 'female)
      (format #t "~a-~a, ~a-~a~%" n1 g1 n2 g2))))

(define (example-3)
  (with-slots ((n name) age) (make <person> :name "Hoge" :age 20)
    (format #t "~a is ~d years old.~%" n age)))

example-1 は昨日のものと同じ。ただしインスタンス生成は一度だけ行われるかどうかチェック。example-2 は Common Lisp の with-slots のように、束縛するシンボルも指定できるようにしたもの。example-3 は example-1 と example-2 の混合。ただし今回の with-slots ではエラーになってしまうため、今後の課題。

example-1, 2 両方に対応できるよう with-slots マクロはつぎようにしてみた。基本は example-2 に対応する形としておいて、example-1 のようにシンボルが省略された形で使われたときは、スロット名をそのままシンボルとして扱うようにして内部的に with-slots を使うように。

(define-syntax with-slots
  (syntax-rules ()
    ;; matches with examle-2   
    ((_ ((var slot) ...) expr body ...)
     (let ((obj expr))
       (let ((var (slot-ref obj 'slot)) ...)
         body ...)))
    ;; matches with example-1
    ((_ (slot ...) expr body ...)
     (with-slots ((slot slot) ...) expr body ...))))

これでテスト。

gosh> (example-1)
[Hoge] is born.
Hoge male 20
#<undef>
gosh> (example-2)
[Hoge] is born.
[Fuga] is born.
Hoge-male, Fuga-female
#<undef>

正しく動作しているようだ。先に書いたように example-3 はまだ対応できていない。

gosh> (example-3)
[Hoge] is born.
*** ERROR: object of class #<class <person>> doesn't have such slot: (n name)
Stack Trace:
_______________________________________
  0  (slot-ref obj '(n name))
        [unknown location]

もう一工夫必要そうだ。ちなみに with-slots は Emacs の場合、

(put 'with-slots 'scheme-indent-function 2)

define-syntax は直感的で良いし、パターンマッチングを採用したあたりが面白い。マッチングって以前少しだけ勉強した OCaml を思い出させる。ちなみに、Ocaml は面白いなと思ったのだけど、Scheme を使っているときのような、ノリというかツボというか、うまく表現できない感覚が得られず、結局今は使ってない。計算がむちゃくちゃ速いのはすごくいいなと思うんだけど。