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