RacketのMacroを調べてみる (7)
ここまでの集大成としてstruct
もどきを作ります
こんな風に使えるものです
> (our-struct person (name age))
> (define p (person "john" 24))
> p
'#(person "john" 24)
> (person-name p)
"john"
> (person-age p)
24
では定義です
(define-syntax (our-struct stx)
(syntax-case stx ()
[(_ id (fields ...))
(for-each (lambda (x) (unless (identifier? x)
(raise-syntax-error #f "not an identifier" stx x)))
(cons #'id (syntax->list #'(fields ...))))
パターンと実行部分の間に式をひとつ入れると「fender」になります
fenderが真なら普通に実行
fenderが偽なら、パターンにマッチしなかったことになって次のパターンを探しにいきます
とはいってもここではraise-syntax-error
でエラーを上げていますので
その場でコンパイルが終了します
syntax->list
は、リスト全体がsyntaxになったものをばらして
リスト要素ひとつずつのsyntaxをリストにして返します
(with-syntax ([pred-id (format-id stx "~a?" #'id)])
#`(begin
#`
は`
のsyntax版です`
が(quasiquote ...)
の短縮形であるように、#`
は(quasisyntax ...)
の短縮形ですquasisyntax
の中に書かれた式は基本的にそのまま返されますが、#,
(unsyntax
)が付いた式は評価して返されます#,@
(unsyntax-splicing
)も同様ですが、リストがひと皮むけて返されます
ここからマクロ本体です
まずは構造体をvectorとして宣言する部分のテンプレート
(define (id fields ...)
(apply vector (cons 'id (list fields ...))))
次はid型のstructかどうかの述語を定義する部分のテンプレート
(define (pred-id v)
(and (vector? v)
(eq? (vector-ref v 0) 'id)))
最後にstructのメンバにアクセスするための関数を定義する部分のテンプレート
#,@(for/list ([x (syntax->list #'(fields ...))]
[n (in-naturals 1)])
in-naturalsは自然数をひとつずつ返してくれるsequenceを返します
vectorへのインデックスとして利用します
(with-syntax ([acc-id (format-id stx "~a-~a" #'id x)]
[ix n])
#`(define (acc-id v)
(unless (pred-id v)
(error 'acc-id "~a is not a ~a struct" v 'id))
(vector-ref v ix))))))]))
ここの#`
は中に#,
がないので#'
でよさそうなんですがなんでしょうか
structねえぞ!と思ったらこうやって作れるというのが
lisp好きなひとにはたまらないんでしょうねえ
達成!