HOME |
14. ヴェクトルと構造体 |
もうひとつの Scheme 入門 |
16. 継続 |
書き込む |
マクロとは式の変換です。 式が評価される前に、または、コンパイル時に式が変換されます。 そして、変換後の式が初めからソースコードに書いてあったかのように処理が行われます。
Common Lisp のマクロ定義はかなり複雑ですが、R6RS に準拠した Scheme では syntax-rules という形式によって比較的簡単に定義できます。 syntax-rules を使うと変数補足などのわずらわしいことを気にしないで、 ”この式をこういう式に変換しろ”ということを直接的に書くことができます。
ただし、syntax-rules で記述できないマクロを書くのは Common Lisp より複雑になります。
簡単な例を示して説明しましょう。 [code 1] は変数に '() を代入するマクロです。
[code 1]
(define-syntax nil!
(syntax-rules ()
((_ x)
(set! x '()))))
syntax-rules の 2 番目の引数は、もとの式 → 変換後の式 を記述した組です。
また、_ はマクロ名を表します。
つまり、[code 1] の意味は、(nil! x) という式を
(set! x '()) に変換しろということです。これは関数で書くことはできません。関数で書くと、クロージャーの働きにより、関数の外の変数 と内部の変数は別の変数になり、関数が自分の外の変数を変化させることはできないからです。 試しに [code 1] の関数版を書いてどうなるか見てみましょう。
[code 1']
(define (f-nil! x) (set! x '()))
> (define a 1) a > (f-nil! 'a) a a 1 ; a の値は変わらない > (nil! a) 1 > a () ; a が '() になった。
もうひとつ簡単な例を示しましょう。predicate が満たされるとき複数の式が実行されるマクロ when を書いてみましょう。
[code 2]
(define-syntax when
(syntax-rules ()
((_ pred b1 ...)
(if pred (begin b1 ...)))))
[code 2] で出てきた ... は 0 個を含む任意個の式を表します。
[code 2] の意味は、
(when pred b1 ...)を
(if pred
(begin
b1
...))
に変換するということです。
これも、特殊形式 if に変換されるマクロですから、関数で書くことはできません。使用例は以下のようになります。
(let ((i 0))
(when (= i 0)
(display "i == 0")
(newline)))
i == 0
;Unspecified return value
簡単なマクロの実用的なものとして while と for を挙げておきます。while は predicate が成り立つ間
本体を実行し、for は数を表す変数がある範囲内にある間処理を実行します。[code 3]
(define-syntax while
(syntax-rules ()
((_ pred b1 ...)
(let loop () (when pred b1 ... (loop))))))
(define-syntax for
(syntax-rules ()
((_ (i from to) b1 ...)
(let loop((i from))
(when (< i to)
b1 ...
(loop (1+ i)))))))
実行例を以下に示します。
(let ((i 0))
(while (< i 10)
(display i)
(display #\Space)
(set! i (+ i 1))))
0 1 2 3 4 5 6 7 8 9
;Unspecified return value
(for (i 0 10)
(display i)
(display #\Space))
0 1 2 3 4 5 6 7 8 9
;Unspecified return value
[code 4]
(define-syntax incf
(syntax-rules ()
((_ x) (begin (set! x (+ x 1)) x))
((_ x i) (begin (set! x (+ x i)) x))))
> (let ((i 0) (j 0))
(incf i)
(incf j 3)
(display (list 'i '= i))
(newline)
(display (list 'j '= j)))
(i = 1)
(j = 3)
[code 5]
(define-syntax my-and
(syntax-rules ()
((_) #t)
((_ e) e)
((_ e1 e2 ...)
(if e1
(my-and e2 ...)
#f))))
(define-syntax my-or
(syntax-rules ()
((_) #f)
((_ e) e)
((_ e1 e2 ...)
(let ((t e1))
(if t t (my-or e2 ...))))))
[code 6]
(define-syntax my-cond
(syntax-rules (else)
((_ (else e1 ...))
(begin e1 ...))
((_ (e1 e2 ...))
(when e1 e2 ...))
((_ (e1 e2 ...) c1 ...)
(if e1
(begin e2 ...)
(cond c1 ...)))))
以下に比較的簡単な例を挙げます。
[code 7]
(define-syntax show-vars
(lambda (x)
(syntax-case x ()
[(_) #''shown]
[(_ e1 e2 ...)
#'(begin (display 'e1) (display "->") (display e1) (newline) (show-vars e2 ...))])))
(define-syntax aif
(lambda (x)
(syntax-case x ()
[(k c b ...)
(with-syntax
([it (datum->syntax #'k 'it)])
#'(let ((it c))
(if it b ...)))])))
最初のマクロ show-vars は変数の値を表示するマクロです。以下のように使います。
> (let ((i 0) (j 1) (k 2)) (show-vars i j k))
i->0
j->1
k->2
shown
2 番目の aif は代名詞マクロです。predicate の結果を it として参照できます。
datum->syntax の2番目の引数を 'it
とすることで代名詞 it を意図的に捕捉しています。
使用例は以下の通りです。
> (let ((i 4))
(aif (memv i '(2 4 6 8))
(car it) #f))
4
Common Lisp ではマクロを書くにはそれなりの熟練が必要ですが、 Scheme の syntax-rules を使うと比較的簡単にマクロを書くことができます。
syntax-case を使うと、Common Lisp で定義できるマクロのほとんどをより安全に定義することができます。 ただ、syntax-case はかなり難易度が高いです。
(define-syntax unless
(syntax-rules ()
((_ pred b1 ...)
(if (not pred)
(begin
b1 ...)))))
(define-syntax decf
(syntax-rules ()
((_ x) (begin (set! x (- x 1)) x))
((_ x i) (begin (set! x (- x i)) x))))
(define-syntax for
(syntax-rules ()
((_ (i from to) b1 ...)
(let loop((i from))
(when (< i to)
b1 ...
(loop (1+ i)))))
((_ (i from to step) b1 ...)
(let loop ((i from))
(when (< i to)
b1 ...
(loop (+ i step)))))))
(define-syntax my-let*
(syntax-rules ()
((_ ((p v)) b ...)
(let ((p v)) b ...))
((_ ((p1 v1) (p2 v2) ...) b ...)
(let ((p1 v1))
(my-let* ((p2 v2) ...)
b ...)))))
HOME |
14. ヴェクトルと構造体 |
もうひとつの Scheme 入門 |
16. 継続 |
書き込む |