マクロ(Macro)


マクロ定義

多くの Lisp 系の言語には強力なマクロ機能が付加されており、 Scheme でも R5RS から標準となりました。 但し、MzScheme/DrScheme には標準と異なるマクロ機能がついています。 以下は、MzScheme/DrScheme をもとに説明します。

で、マクロってどんなことが出来るのでしょう? 手短にいうと、新たな構文を導入し、既存の式への変換ルールを与えることが出来ます。 これは、C 言語の Macro でも同じです。変換ルールの記述能力が圧倒的に異なりますが。

さきほど紹介した遅延評価は、実はラムダ式を使えば似たような事が出来ると、 以前ラムダ式の練習問題でやりました。以下の通りです。 head, tail が car, cdr に相当します。


(define (infinite-enumerate n)
  (cons n
        (lambda () (infinite-enumerate (+ n 1)))))

(define (head inf-list) (car inf-list))
(define (tail inf-list) 
  (let ((rest (cdr inf-list)))
    (rest)))
ここでは、head や tail は普通の関数呼出しの様に作る事ができています。 あと、

  (lazy-cons n (infinite-enumerate (+ n 1)))
の様に書くことができれば、 head, tail, lazy-cons をつかって簡単に遅延評価版リストを使えそうです。 しかし、lazy-cons という関数を定義することは出来ません。 なぜなら、関数呼出しの場合、 引数である(infinite-enumerate (+ n 1)) をまず評価してしまうからです。

この場合やりたいことは、


  (lazy-cons n (infinite-enumerate (+ n 1)))
  => (cons n (lambda () (infinite-enumerate (+ n 1))))
に変換することです。そこで DrScheme では以下のようにマクロを書くことが出来ます。

(define (lazy-cons-expander x y)        ; これは、lazy-cons をどの様に展開するかを示す関数
  (list 'cons x (list 'lambda '() y)))  

; lazy-cons-expander の 使用例
; > (lazy-cons-expander 'n '(infinite-enumerate (+ n 1)))
; (cons n (lambda () (infinite-enumerate (+ n 1))))

; lazy-cons というマクロは lazy-cons-expnader で展開して下さいという命令
(define-macro lazy-cons lazy-cons-expander) 

; これで、lazy-cons というマクロが使用可能になり、勝手に先程の様に展開してくれます。
; lazy-cons 使用例
(define (infinite-enumerate2 n)
  (lazy-cons n
	     (infinite-enumerate2 (+ n 1))))
これで、lazy-cons, head, tail で見た目もきれいに自作遅延評価が使えるようになりました。

パターンマッチング

マクロを使った言語拡張の例として、ML などでしられた pattern match を紹介しておきます。 DrSchemeでは、library として提供されています。 とっても便利です。

> (require-library "match.ss" "mzlib")  ; mzlib というcollection にある "match.ss" を使う
> (define (calc-total-weight x)         ; 二分木の総重量をもとめる
    (match x              ; x が
      [('leaf weight)     ; ('leaf weight) という形にあうなら,
                          ; つまり、先頭が leaf というシンボルで長さが 2 のリストなら
                          ; weight という変数に weight が該当する部分を束縛し、
       weight]            ; weight を返す。
      [('node left right) ; ('node left right) という形にあうなら、
                          ; つまり、先頭が node というシンボルで長さ 3 のリストなら
                          ; 第二、三要素を left, right に束縛し、
       (+ (calc-total-weight left)      ; 以下の式の値を返す
	  (calc-total-weight right))]))
> (calc-total-weight '(node (leaf 3) (node (leaf 4) (leaf 5))))
12

99.10.7/ Tomio KAMADA: kamada@cs.kobe-u.ac.jp