kb84tkhrのブログ

何を書こうか考え中です あ、あと組織とは関係ないってやつです 個人的なやつ

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好きなひとにはたまらないんでしょうねえ

 

達成!