R5RSマクロその1

R5RSマクロはパターンマッチの仕組みを利用して、式の変換規則を定義する。
こんな感じ。

(define-syntax マクロ名
  (syntax-rules ()
    (パターン1 テンプレート1)
    (パターン2 テンプレート2)
    ;; 中略...
    (パターンn テンプレートn)))

パターン1、パターン2と順に調べていき、マッチしたパターンi対するテンプレートiへと展開される。


では、本に載っているコード例をみてみる。
以下は組み込みのorマクロを模倣したもの(それとも本当にこう定義されているのかな?)。

(define-syntax or
  (syntax-rules ()
    ((or) #f)
    ((or e) e)
    ((or e1 e2 ...)
     (let ((tmp e1))
       (if tmp tmp (or e2 ...))))))

orは

  1. 引数が無ければ#f
  2. 引数が1個ならば、その引数そのまま
  3. 引数が1個以上ならば
    • 先頭の引数が真となれば、その引数
    • さもなくば、残りの引数で再帰

という定義。マクロの定義にも再帰が使えるんやね。


「...」は繰返しをあらわし、

(or e1 e2 ...)

だったら、orに1個以上の引数が渡されたときにマッチする。
例えば

(e1 (e2 e3) ...)

だったら、

(a)
(x (y z))
(foo (bar baz) (() qux))

などにマッチする。


このorの定義を読んで思ったのは、letせずに単に

(if e1 e1 (or e2 ...))

で良いんじゃないか、ってこと。
多分これは、もしe1の評価が大きなコストを伴う場合、e1が真となる場合にもう一回評価されてしまい、効率が悪いから...かな?
letで束縛しとけば、評価は一回で済むしね。


演習問題ということで、andに相当するmy-andを定義してみた。

gosh> (define-syntax my-and
        (syntax-rules ()
          ((my-and) #t)
          ((my-and e) e)
          ((my-and e1 e2 ...)
            (let ((tmp (my-and e2 ...)))
              (if e1 tmp e1)))))
#<undef>
gosh> (my-and)
#t
gosh> (my-and 1)
1
gosh> (my-and #f)
#f
gosh> (my-and 1 2)
2
gosh> (my-and 1 2 #f)
#f
gosh> (my-and 1 2 #f 3 4)
#f

いけてる...と思う。