Skip to content

Instantly share code, notes, and snippets.

@MiyamonY
Last active March 19, 2021 12:35
Show Gist options
  • Save MiyamonY/451dde37696058e7c327782abaf2bf28 to your computer and use it in GitHub Desktop.
Save MiyamonY/451dde37696058e7c327782abaf2bf28 to your computer and use it in GitHub Desktop.
racket macro detail

racket macro

syntax transformer

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オブジェクト

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"))

match

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

マクロ定義をする際にパターンマッチを加えたものが 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)

with-syntax

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment