---
# System prepended metadata

title: 16.2.5
tags: [The Racket Guide]

---

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