syntax transformerとはsyntaxを受け取ってsyntaxを返す関数のこと.
define-syntax
によりtransfomer束縛を作っている.syntaxの環境にfooを入れ
てfooが出現するたびにfooのsyntax transformerを行っている.
#lang racket
(define-syntax foo
(lambda (stx) (syntax "I am foo")))
(foo)
lambda関数のように引数にsyntaxオブジェクトをとることができる.
#lang racket
(define-syntax (foo stx)
(syntax "I am foo"))
(foo)
syntaxリテラルとして ~ #’ ~ が使える.
#lang racket
(define-syntax (foo stx)
#'"I am foo")
(foo)
#lang racket
(define-syntax (say-hi stx)
#'(displayln "hi"))
(say-hi)
syntaxオブジェクトはソースファイル,行番号,列からなる.
#lang racket
(define-syntax (show-me stx)
(print stx)
#'(void))
(show-me '(+ 1 2))
マクロ呼び出しも含んだsyntaxオブジェクトが返る.
syntaxオブジェクトの定義も可能.この時は(もちろん)マクロ呼び出しは入らない.
#lang racket
(define stx #'(if x (list "true") #f))
stx
syntaxオブジェクトからソースファイル,行番号,列を取得することができる.
#lang racket
(define stx #'(list a b c))
(syntax-source stx)
(syntax-line stx)
(syntax-column stx)
(syntax->datum stx)
(syntax-e stx)
(syntax->list stx)
syntax->datum
によりsyntaxオブジェクトからsyntaxを取り出し,S式に変換できる.また, syntax-e
によりsyntaxオブジェクトの一つ下に潜れ
る.~syntax->list~ は syntax-e
とほぼ同じ同じ働きをする.
与えられた引数を逆転するマクロを作成する.
#lang racket
(define-syntax (reverse-me stx)
(datum->syntax stx (reverse (cdr (syntax->datum stx)))))
(reverse-me "backwards" "am" "i" list)
(reverse-me "i" "am" "backwards" list)
はsyntax transformerによって (list "i" "am" "backwards")
に変換される.そして,そのS式が評価され ("i" "am" "backwards")
となる.
datam->synax
でsyntaxオブジェクトを渡すことで,lexicalコンテキスト(ファイル名,行数など)を引き継いでいる.
関数として定義した場合,関数呼び出し時に評価されてしまう.
#lang racket
(define-syntax (our-if stx)
(define xs (syntax->list stx))
(datum->syntax stx `(cond [,(cadr xs) ,(caddr xs)]
[else ,(cadddr xs)])))
(our-if #t
(displayln "true")
(displayln "false"))
(our-if #f
(displayln "true")
(displayln "false"))
require (for-syntax <module>)
を使用することでマクロ定義で match
が使用できる.
#lang racket
(require (for-syntax racket/match))
(define-syntax (our-if-with-match stx)
(match (syntax->list stx)
[(list _ condition true-expr false-expr)
(datum->syntax stx `(cond [,condition ,true-expr]
[else ,false-expr]))]))
(our-if-with-match #t (displayln "true") (displayln "false"))
(our-if-with-match #f (displayln "true") (displayln "false"))
マクロ定義をする際にパターンマッチを加えたものが syntax-case
となる.
#lang racket
(define-syntax (our-if-using-syntax-case stx)
(syntax-case stx ()
[(_ condition true-expr false-expr)
#'(cond [condition true-expr]
[else false-expr])]))
(our-if-using-syntax-case #t "true" "false")
さらにシンプル場合だと define-syntax-rule
が使用できる.
#lang racket
(define-syntax-rule (our-if-using-syntax-rule condition true-expr false-expr)
(cond [condition true-expr]
[else false-expr]))
(our-if-using-syntax-rule #f "true" "false")
syntax-case
の2番目の引数は id
のリストである.このリストにのっている id
は束縛されいない.そのため,下の例では変換後のsyntax =>
が束縛されていないためエラーとなる.
#lang racket
(syntax-case #'(ops 1 2 => 3 +) ()
[(_ x => ... op) #'(op x => ...)])
#lang racket
(syntax-case #'(ops 1 2 => 3 +) (=>)
[(_ x => ... op) #'(op x => ...)])
構造体のように定義を行うとアクセス用の関数にtransformするマクロを定義する. (hyphen-define a b (args) body)
を (define (a-b args) body)
とtransformしたい.
#lang racket
(define-syntax (hyphen-define/error1 stx)
(syntax-case stx ()
[(_ a b (args ...) body0 body ...)
(let ([name (string->symbol (format "~a-~a" a b))])
#'(define (name args ...)
body0 body ...))]))
(hyphen-define hyphen add (x y) (print (+ x y)))
(hyphen-add 1 2)
結果は pattern variable cannot be used outside of a template
となる.パターン変数は #'
(レンプレート)の中に入れる必要がある.
#lang racket
(define-syntax (hyphen-define/error2 stx)
(syntax-case stx ()
[(_ a b (args ...) body0 body ...)
(let ([name (string->symbol (format "~a-~a" #'a #'b))])
#'(define (name args ...)
body0 body ...))]))
(hyphen-define hyphen add (x y) (print (+ x y)))
(hyphen-add 1 2)
次は unbound identifier
となり name
が束縛されている.そこでsyntaxオブジェクトに変換してマッチさせてやる.
#lang racket
(define-syntax (hyphen-define/error3 stx)
(syntax-case stx ()
[(_ a b (args ...) body0 body ...)
(syntax-case (datum->syntax #'a
(string->symbol (format "~a-~a" #'a #'b))) ()
[name
#'(define (name args ...) body0 body ...)])]))
(expand-once (hyphen-define/error3 hyphen add (x y) (print (+ x y))))
またしても unbound identifier
エラーとなる.今回はsyntaxオブジェクトに対して format
による文字列に変換を行ない,関数名を生成しているため関数名が (|#<syntax:/tmp/babel-fe7mRj/racket-x9BcKA:10:35 hyphen>-#<syntax:/tmp/babel-fe7mRj/racket-x9BcKA:10:42 add>| x y)
のようになっている.
#lang racket
(define-syntax (hyphen-define stx)
(syntax-case stx ()
[(_ a b (args ...) body0 body ...)
(syntax-case (datum->syntax #'a
(string->symbol (format "~a-~a" (syntax->datum #'a) (syntax->datum #'b)))) ()
[name
#'(define (name args ...) body0 body ...)])]))
(hyphen-define hyphen add (x y) (print (+ x y)))
(hyphen-add 1 2)