--- title: 16.2.6 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.6 一般的なフェーズレベル フェーズは、次のプロセスで使用されるコードを生成するプロセスのパイプラインにおける計算を分離する方法と考えることができます。(例えば、プリプロセッサプロセス、コンパイラ、アセンブラで構成されるパイプラインなどです。) この目的のために2つのRacketプロセスを起動することを想像してみてください。ソケットやファイルなどのプロセス間通信チャネルを無視した場合、一方のプロセスの標準出力から他方のプロセスの標準入力にパイプされたテキスト以外のものを共有する方法がありません。同様に、Racket では、モジュールの複数の呼び出しを、フェーズごとに分けて同じプロセス内に存在させることができます。Racket では、このようなフェーズの分離が強制され、異なるフェーズは、あるフェーズの出力が次のフェーズで使用されるコードとなる、マクロ展開のプロトコルを介した以外の方法では通信できません。 --- ## 16.2.6.1 フェーズとバインディング 識別子のすべてのバインディングは、特定のフェーズに存在します。バインディングとそのフェーズの間のリンクは、整数のフェーズレベルで表されます。フェーズレベル0は、「プレーン」(または「ランタイム」)定義に使われるフェーズで、次のようになります。 ```ocaml (define age 5) ``` はフェーズレベル0にageのバインディングを追加します。識別子ageはbegin-for-syntaxを使ってより高いフェーズレベルで定義できます。 ```ocaml (begin-for-syntax (define age 5)) ``` 1つの begin-for-syntax ラッパーで、age はフェーズ・レベル 1 で定義されます。この2つの定義を同じモジュールやトップレベルの名前空間で簡単に混在させることができ、異なるフェーズ・レベルで定義された2つの年齢の間で衝突することはありません。 ```ocaml > (define age 3) > (begin-for-syntax (define age 9)) ``` フェーズレベル 0 での年齢バインディングの値は 3 で、フェーズレベル 1 での年齢バインディングの値は 9 です。 構文オブジェクトは、バインディング情報をファーストクラスの値として取り込みます。したがって `#'age` は年齢のバインディングを表す構文オブジェクトですが、年齢は 2 つ (フェーズレベル 0 とフェーズレベル 1) あるので、どちらの年齢を表すのでしょうか。実際には、Racketは#'ageにすべてのフェーズレベルの語彙情報を持たせているため、答えは#'ageが両方を捉えるということになります。 ageが捕捉するageの関連する結合は、最終的に#'ageが使用されるときに決定されます。例として、#'ageをパターン変数にバインドしてテンプレートで使えるようにし、テンプレートを評価します。 <div class="box">ここではフェーズを示すためにevalを使いますが、evalの注意点についてはReflection and Dynamic Evaluationを参照してください。 </div> ```ocaml > (eval (with-syntax ([age #'age]) #'(displayln age))) 3 ``` ageがフェーズ0レベルで使用されているため、結果は3となります。再度、begin-for-syntaxの中でageを使用して試してみます。 ```ocaml > (eval (with-syntax ([age #'age]) #'(begin-for-syntax (displayln age)))) 9 ``` この場合、答えは9です。これは、ageを0ではなくフェーズ・レベル1で使用しているからです(つまり、begin-for-syntaxはその式をフェーズ・レベル1で評価します)。このように、#'ageという同じ構文オブジェクトを使って、フェーズ・レベル0とフェーズ・レベル1という2つの異なる方法で使用することができました。 構文オブジェクトは、それが最初に存在した瞬間から語彙的なコンテキストを持ちます。モジュールから提供された構文オブジェクトは、その語彙的なコンテキストを保持しているため、使用時のコンテキストではなく、ソースモジュールのコンテキストでバインディングを参照します。次の例では、button をフェーズレベル 0 で定義して 0 にバインドし、see-button はモジュール a の button のシンタックスオブジェクトをバインドしています。 ```ocaml > (module a racket (define button 0) (provide (for-syntax see-button)) ; Why not (define see-button #'button)? We explain later. (define-for-syntax see-button #'button)) > (module b racket (require 'a) (define button 8) (define-syntax (m stx) see-button) (m)) > (require 'b) 0 ``` mマクロの結果は、see-buttonの値であり、aモジュールの語彙的文脈では#'buttonとなります。bに別のボタンがあっても、#'button(see-buttonに束縛された値)の語彙的な文脈がaであるため、2つ目のボタンがRacketを混乱させることはありません。 see-buttonはdefine-for-syntaxで定義したことにより、フェーズレベル1で結合されていることに注意してください。フェーズレベル1が必要なのは、mがマクロであるため、その本体が定義のコンテキストよりも1つ上のフェーズで実行されるからです。mはフェーズレベル0で定義されているので、そのボディはフェーズレベル1であり、ボディが参照するバインディングはすべてフェーズレベル1でなければなりません。 --- ## 16.2.6.2 フェーズとモジュール フェーズレベルは、モジュールに関連する概念です。requireで他のモジュールからインポートする場合、Racketではインポートされたバインディングを元のものとは異なるフェーズレベルに移行させることができます。 ```ocaml (require "a.rkt") ; import with no phase shift (require (for-syntax "a.rkt")) ; shift phase by +1 (require (for-template "a.rkt")) ; shift phase by -1 (require (for-meta 5 "a.rkt")) ; shift phase by +5 ``` つまり、requireでfor-syntaxを使うと、そのモジュールのすべてのバインディングのフェーズレベルが1つ上がることになります。フェーズレベル0で定義されているバインディングをfor-syntaxでインポートすると、フェーズレベル1のバインディングになります。 ```ocaml > (module c racket (define x 0) ; defined at phase level 0 (provide x)) > (module d racket (require (for-syntax 'c)) ; has a binding at phase level 1, not 0: #'x) ``` フェーズレベル0で#'button syntaxオブジェクトのバインディングを作成しようとするとどうなるか見てみましょう。 ```ocaml > (define button 0) > (define see-button #'button) ``` これで button と see-button の両方がフェーズ 0 で定義されました。 #'button の語彙的なコンテキストは、フェーズ 0 に button のバインディングがあることを知っています。 実際、 see-button を評価しようとすると、物事はうまくいっているように見えます。 ```ocaml > (eval see-button) 0 ``` では、see-buttonをマクロで使ってみましょう。 ```ocaml > (define-syntax (m stx) see-button) > (m) see-button: undefined; cannot reference an identifier before its definition in module: top-level ``` 明らかに、see-button はフェーズ レベル 1 で定義されていないので、マクロ本体内で参照することはできません。see-button を別のモジュールで使用するには、ボタンの定義をモジュールに入れて、それをフェーズ レベル 1 でインポートしてみましょう。そうすると、フェーズレベル1で see-button が得られます。 ```ocaml > (module a racket (define button 0) (define see-button #'button) (provide see-button)) > (module b racket (require (for-syntax 'a)) ; gets see-button at phase level 1 (define-syntax (m stx) see-button) (m)) eval:1:0: button: unbound identifier; also, no #%top syntax transformer is bound in: button ``` Racketによると、buttonはunboundになったそうです。aがフェーズレベル1でインポートされると、以下のようなバインディングになります。 ``` button at phase level 1 see-button at phase level 1 ``` マクロ m は、フェーズレベル 1 の see-button のバインディングを見ることができ、フェーズレベル 1 の button のバインディングを参照する #'button 構文オブジェクトを返します。しかし、mの使用はフェーズレベル0であり、bにはフェーズレベル0のボタンはありません。 ``` button at phase level 0 see-button at phase level 1 ``` このシナリオでは、see-button がフェーズ レベル 1 にバインドされているので、マクロで see-button を使用できます。マクロを展開すると、フェーズ レベル 0 のボタン バインディングが参照されます。 see-button を (define see-button #'button) で定義することは本質的に間違っているわけではなく、see-button をどのように使用するかによります。例えば、mはbegin-for-syntaxを使ってフェーズレベル1のコンテキストに置かれるので、分別を持ってsee-buttonを使用するようにアレンジすることができます。 ```ocaml > (module a racket (define button 0) (define see-button #'button) (provide see-button)) > (module b racket (require (for-syntax 'a)) (define-syntax (m stx) (with-syntax ([x see-button]) #'(begin-for-syntax (displayln x)))) (m)) 0 ``` この場合、モジュールbはボタンとsee-buttonの両方がフェーズレベル1でバインドされている。マクロの展開は ```ocaml (begin-for-syntax (displayln button)) ``` となり、buttonがフェーズレベル1にバインドされているため、動作します。 ここで、位相レベル 0 と位相レベル 1 の両方で a をインポートして位相システムをごまかそうとするかもしれません。そうすると、次のようなバインディングになります。 ``` button at phase level 0 see-button at phase level 0 button at phase level 1 see-button at phase level 1 ``` マクロで see-button を使えばうまくいくと思うかもしれませんが、そうではありません。 ```ocaml > (module a racket (define button 0) (define see-button #'button) (provide see-button)) > (module b racket (require 'a (for-syntax 'a)) (define-syntax (m stx) see-button) (m)) eval:1:0: button: unbound identifier; also, no #%top syntax transformer is bound in: button ``` モジュール a の定義では,変数 see-button はフェーズ 0 にあり,その値は button のシンタックス・オブジェクトであり,唯一目に見える button のバインディングが同じフェーズにあることを示しています(これが重要な詳細です).(議論したように、for-syntax importは両方の位相レベルを1つ上にシフトするので、モジュールbで使用されるsee-buttonの位相1のバインディングは、位相1にシフトされたモジュールaからのバインディングであり、位相1でaのbuttonを参照していることになります。モジュールbの中に、モジュールaの別のインスタンスからの相0の変数buttonもあるという事実は、aからの(シフトされた)see-buttonからそれに到達する方法がないので、重要ではありません。 このようなインスタンス間の位相レベルの不一致は、syntax-shift-phase-levelで修復できます。ここで、#'buttonのような構文オブジェクトは、すべてのフェーズレベルの語彙情報を取り込むことを思い出してください。ここでの問題は、see-buttonがフェーズ1で起動され、フェーズ0で評価できるシンタックスオブジェクトを返す必要があることです。しかし、syntax-shift-phase-level を使用すると、see-button が異なる相対的なフェーズ レベルで #'button を参照するようにできます。この場合、位相シフトを-1にして、位相1のsee-buttonが位相0の#'buttonを参照するようにします(位相シフトはすべてのレベルで行われるため、位相0のsee-buttonが位相-1の#'buttonを参照するようにもなります)。 syntax-shift-phase-level は、単にフェーズ間の参照を作成するだけであることに注意してください。その参照を機能させるためには、やはり両方のフェーズでモジュールをインスタンス化して、参照とそのターゲットがバインディングを利用できるようにする必要があります。したがって、モジュール 'b では、モジュール 'a をフェーズ 0 とフェーズ 1 の両方で (require 'a (for-syntax 'a)) を使用してインポートします。これでマクロmが動作します。 ```ocaml > (module a racket (define button 0) (define see-button (syntax-shift-phase-level #'button -1)) (provide see-button)) > (module b racket (require 'a (for-syntax 'a)) (define-syntax (m stx) see-button) (m)) > (require 'b) 0 ``` ところで、フェーズ0にバインドされているsee-buttonはどうなるのでしょうか?button自体はフェーズ-1に束縛されていないので、フェーズ0でsee-buttonを評価しようとするとエラーになります。つまり、ミスマッチの問題を永久に解決したわけではなく、より厄介でない場所に移動しただけです。 ```ocaml > (module a racket (define button 0) (define see-button (syntax-shift-phase-level #'button -1)) (provide see-button)) > (module b racket (require 'a (for-syntax 'a)) (define-syntax (m stx) see-button) (m)) > (module b2 racket (require 'a) (eval see-button)) > (require 'b2) button: undefined; cannot reference an identifier before its definition in module: top-level ``` 上記のようなミスマッチは、syntax-case や syntax-parse を使用してマクロがリテラルバインディングをマッチさせようとしたときにも発生します。 ```ocaml > (module x racket (require (for-syntax syntax/parse) (for-template racket/base)) (provide (all-defined-out)) (define button 0) (define (make) #'button) (define-syntax (process stx) (define-literal-set locals (button)) (syntax-parse stx [(_ (n (~literal button))) #'#''ok]))) > (module y racket (require (for-meta 1 'x) (for-meta 2 'x racket/base)) (begin-for-syntax (define-syntax (m stx) (with-syntax ([out (make)]) #'(process (0 out))))) (define-syntax (p stx) (m)) (p)) eval:2:0: process: expected the identifier `button' at: button in: (process (0 button)) ``` この例では、make は y でフェーズレベル 2 で使用されており、#'button syntax オブジェクトを返します。これは、x の内部でフェーズレベル 0 で、y でフェーズレベル 2 で (for-meta 2 'x) からバインドされた button を参照しています。プロセスマクロは、(for-meta 1 'x)からフェーズレベル1でインポートされており、buttonがフェーズレベル1でバインドされるべきであることを知っています。プロセス内で syntax-parse が実行されると、フェーズ レベル 1 でバインドされた button を探していますが、フェーズ レベル 2 のバインドしか見当たらず、一致しません。 この例を修正するには、xに対して相レベル1でmakeを提供し、yの相レベル1でimportします。 ```ocaml > (module x racket (require (for-syntax syntax/parse) (for-template racket/base)) (provide (all-defined-out)) (define button 0) (provide (for-syntax make)) (define-for-syntax (make) #'button) (define-syntax (process stx) (define-literal-set locals (button)) (syntax-parse stx [(_ (n (~literal button))) #'#''ok]))) > (module y racket (require (for-meta 1 'x) (for-meta 2 racket/base)) (begin-for-syntax (define-syntax (m stx) (with-syntax ([out (make)]) #'(process (0 out))))) (define-syntax (p stx) (m)) (p)) > (require 'y) 'ok ```