--- title: 16.2.5 tags: The Racket Guide slideOptions: theme: white slideNumber: 'c/t' center: false transition: 'none' keyboard: true width: '93%' height: '100%' --- <style> /* basic design */ .reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6, .reveal section, .reveal table, .reveal li, .reveal blockquote, .reveal th, .reveal td, .reveal p { font-family: 'Meiryo UI', 'Source Sans Pro', Helvetica, sans-serif, 'Helvetica Neue', 'Helvetica', 'Arial', 'Hiragino Sans', 'ヒラギノ角ゴシック', YuGothic, 'Yu Gothic'; text-align: left; line-height: 1.8; letter-spacing: normal; text-shadow: none; word-wrap: break-word; color: #444; } .reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5 {font-weight: normal;}, .reveal h6 {font-weight: bold;} .reveal h1, .reveal h2, .reveal h3 {color: #2980b9;} .reveal th {background: #DDD;} .reveal section img {background:none; border:none; box-shadow:none; max-width: 95%; max-height: 95%;} .reveal blockquote {width: 90%; padding: 0.5vw 3.0vw;} .reveal table {margin: 1.0vw auto;} .reveal code {line-height: 1.2;} .reveal p, .reveal li {padding: 0vw; margin: 0vw;} .reveal .box {margin: -0.5vw 1.5vw 2.0vw -1.5vw; padding: 0.5vw 1.5vw 0.5vw 1.5vw; background: #EEE; border-radius: 1.5vw;} /* table design */ .reveal table {background: #f5f5f5;} .reveal th {background: #444; color: #fff;} .reveal td {position: relative; transition: all 300ms;} .reveal tbody:hover td { color: transparent; text-shadow: 0 0 3px #aaa;} .reveal tbody:hover tr:hover td {color: #444; text-shadow: 0 1px 0 #fff;} /* blockquote design */ .reveal blockquote { width: 90%; padding: 0.5vw 0 0.5vw 6.0vw; font-style: italic; background: #f5f5f5; } .reveal blockquote:before{ position: absolute; top: 0.1vw; left: 1vw; content: "\f10d"; font-family: FontAwesome; color: #2980b9; font-size: 3.0vw; } /* font size */ .reveal h1 {font-size: 5.0vw;} .reveal h2 {font-size: 3.0vw;} .reveal h3 {font-size: 2.8vw;} .reveal h4 {font-size: 2.6vw;} .reveal h5 {font-size: 1.8vw;} .reveal h6 {font-size: 2.2vw;} .reveal section, .reveal table, .reveal li, .reveal blockquote, .reveal th, .reveal td, .reveal p {font-size: 2.2vw;} .reveal code {font-size: 1.6vw;} /* new color */ .red {color: #EE6557;} .blue {color: #16A6B6;} /* split slide */ #right {left: -18.33%; text-align: left; float: left; width: 50%; z-index: -10;} #left {left: 31.25%; text-align: left; float: left; width: 50%; z-index: -10;} </style> <style> /* specific design */ .reveal h1 { margin: 0% -100%; padding: 2% 100% 4% 100%; color: #fff; background: #fffa5e; /* fallback for old browsers */ background: linear-gradient(-45deg, #f7f439, #54ffa4, #23A6D5, #238ed5); background-size: 200% 200%; animation: Gradient 60s ease infinite; } @keyframes Gradient { 0% {background-position: 0% 50%} 50% {background-position: 100% 50%} 100% {background-position: 0% 50%} } .reveal h2 { text-align: center; margin: -5% -50% 2% -50%; padding: 3% 10% 1% 10%; color: #fff; background: #fffa5e; /* fallback for old browsers */ background: -webkit-linear-gradient(to right, #c74646, #fffa5e); /* Chrome 10-25, Safari 5.1-6 */ background: linear-gradient(to right, #c74646, #fffa5e); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ } .reveal h4 { text-align: center; } </style> --- ## 16.2.5 コンパイルとランタイムの段階 マクロのセットがより複雑になると、generate-temporariesのような独自のヘルパー関数を書きたくなるかもしれません。たとえば、優れた構文エラーメッセージを提供するために、swap、rotate、define-cbrはすべて、ソースフォームの特定のサブフォームが識別子であることをチェックする必要があります。このチェックをあらゆる場所で行うために、check-ids関数を使用することができます。 ```ocaml (define-syntax (swap stx) (syntax-case stx () [(swap x y) (begin (check-ids stx #'(x y)) #'(let ([tmp x]) (set! x y) (set! y tmp)))])) (define-syntax (rotate stx) (syntax-case stx () [(rotate a c ...) (begin (check-ids stx #'(a c ...)) #'(shift-to (c ... a) (a c ...)))])) ``` check-ids関数は、syntax->list関数を使って、リストを包んでいるsyntax-objectを、syntax-objectのリストに変換することができます。 ```ocaml (define (check-ids stx forms) (for-each (lambda (form) (unless (identifier? form) (raise-syntax-error #f "not an identifier" stx form))) (syntax->list forms))) ``` しかし、swapとcheck-idsをこのように定義すると、うまくいきません。 ``` > (let ([a 1] [b 2]) (swap a b)) check-ids: undefined; cannot reference an identifier before its definition in module: top-level ``` 問題は、check-ids が実行時の式として定義されているにもかかわらず、swap がコンパイル時にそれを使おうとしていることです。対話型モードでは、コンパイル時と実行時はインターリーブされますが、モジュール本体内ではインターリーブされませんし、先行してコンパイルされたモジュール間でもインターリーブされません。これらすべてのモードでコードを一貫して扱えるようにするために、Racketではフェーズごとにバインディングスペースを分けています。 コンパイル時に参照可能な check-ids 関数を定義するには、begin-for-syntax を使用します。 ```ocaml (begin-for-syntax (define (check-ids stx forms) (for-each (lambda (form) (unless (identifier? form) (raise-syntax-error #f "not an identifier" stx form))) (syntax->list forms)))) ``` このfor-syntaxの定義では、swapが動作します。 ```ocaml > (let ([a 1] [b 2]) (swap a b) (list a b)) '(2 1) > (swap a 1) eval:13:0: swap: not an identifier at: 1 in: (swap a 1) ``` プログラムをモジュールで構成するときに、あるモジュールにヘルパー関数を置いて、他のモジュールに存在するマクロに使わせたい場合があります。その場合、defineを使ってヘルパー関数を書くことができます。 ```ocaml "utils.rkt" #lang racket (provide check-ids) (define (check-ids stx forms) (for-each (lambda (form) (unless (identifier? form) (raise-syntax-error #f "not an identifier" stx form))) (syntax->list forms))) ``` 次に、マクロを実装したモジュールで、ヘルパー関数を(require "utils.rkt")ではなく、(require (for-syntax "utils.rkt"))を使ってインポートします。 ```ocaml #lang racket (require (for-syntax "utils.rkt")) (define-syntax (swap stx) (syntax-case stx () [(swap x y) (begin (check-ids stx #'(x y)) #'(let ([tmp x]) (set! x y) (set! y tmp)))])) ``` モジュールは個別にコンパイルされ、循環的な依存関係を持つことはできないので、swapを実装するモジュールをコンパイルする前に、「utils.rkt」モジュールのランタイムボディをコンパイルすることができます。したがって、(require (for-syntax ....))によってコンパイル時に明示的にシフトされていれば、「utils.rkt」のランタイム定義をswapの実装に使用することができます。 racketモジュールは、実行時とコンパイル時の両方のフェーズで使用できるように、syntax-case、generate-temporaries、lambda、ifなどを提供しています。そのため、racket REPLでsyntax-caseを直接、または定義構文の右辺で使用することができます。 これに対して、racket/baseモジュールは、実行時フェーズでのみこれらのバインディングをエクスポートします。swapを定義した上記のモジュールを、racketではなくracket/base言語を使用するように変更すると、動作しなくなります。require (for-syntax racket/base)) を追加すると、syntax-case などがコンパイル時にインポートされるため、モジュールが再び動作するようになります。 define-syntaxがdefine-syntaxフォームの右手側でローカルマクロを定義するために使用されているとします。その場合、内側のdefine-syntaxの右辺はメタ・コンパイル・フェーズ・レベル(フェーズ・レベル2とも呼ばれる)にあります。syntax-caseをそのフェーズレベルにインポートするには、(require (for-syntax (for-syntax racket/base)))または、同等に、(require (for-meta 2 racket/base))を使用する必要があります。たとえば、以下のようになります。 ```ocaml #lang racket/base (require ;; This provides the bindings for the definition ;; of shell-game. (for-syntax racket/base) ;; And this for the definition of ;; swap. (for-syntax (for-syntax racket/base))) (define-syntax (shell-game stx) (define-syntax (swap stx) (syntax-case stx () [(_ a b) #'(let ([tmp a]) (set! a b) (set! b tmp))])) (syntax-case stx () [(_ a b c) (let ([a #'a] [b #'b] [c #'c]) (when (= 0 (random 2)) (swap a b)) (when (= 0 (random 2)) (swap b c)) (when (= 0 (random 2)) (swap a c)) #`(list #,a #,b #,c))])) (shell-game 3 4 5) (shell-game 3 4 5) (shell-game 3 4 5) ``` 負の位相レベルも存在します。マクロが for-syntax としてインポートされたヘルパー関数を使用し、そのヘルパー関数が syntax によって生成された syntax オブジェクト定数を返す場合、syntax 内の識別子は、マクロを定義するモジュールに関連するランタイム フェーズ レベルでバインディングを持つために、テンプレート フェーズ レベルとも呼ばれるフェーズ レベル -1 でのバインディングが必要になります。 たとえば、以下の例にある swap-stx ヘルパー関数は、構文変換ではなく普通の関数ですが、シェルゲームの結果にスプライシングされる構文オブジェクトを生成します。そのため、ヘルパーを含むサブモジュールは shell-game のフェーズ 1 で (require (for-syntax 'helper)) でインポートする必要があります。 しかし、swap-stx の観点から見ると、その結果は最終的に位相レベル -1 で評価され、shell-game が返す構文が評価されます。言い換えれば、負の位相レベルは逆に正の位相レベルです。つまり、shell-gameの位相1はswap-stxの位相0であり、shell-gameの位相0はswap-stxの位相-1です。 これが、この例がうまくいかない理由です。 ```ocaml #lang racket/base (require (for-syntax racket/base)) (module helper racket/base (provide swap-stx) (define (swap-stx a-stx b-stx) #`(let ([tmp #,a-stx]) (set! #,a-stx #,b-stx) (set! #,b-stx tmp)))) (require (for-syntax 'helper)) (define-syntax (shell-game stx) (syntax-case stx () [(_ a b c) #`(begin #,(swap-stx #'a #'b) #,(swap-stx #'b #'c) #,(swap-stx #'a #'c) (list a b c))])) (define x 3) (define y 4) (define z 5) (shell-game x y z) ``` この例を修正するには、(require (for-template racket/base)) を「ヘルパーサブモジュール」に追加します。 ```ocaml #lang racket/base (require (for-syntax racket/base)) (module helper racket/base (require (for-template racket/base)) ; binds `let` and `set!` at phase -1 (provide swap-stx) (define (swap-stx a-stx b-stx) #`(let ([tmp #,a-stx]) (set! #,a-stx #,b-stx) (set! #,b-stx tmp)))) (require (for-syntax 'helper)) (define-syntax (shell-game stx) (syntax-case stx () [(_ a b c) #`(begin #,(swap-stx #'a #'b) #,(swap-stx #'b #'c) #,(swap-stx #'a #'c) (list a b c))])) (define x 3) (define y 4) (define z 5) (shell-game x y z) (shell-game x y z) (shell-game x y z) ```