<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.3 モジュールのインスタンス化と訪問
モジュールには、関数や構造体タイプの定義だけが含まれていることがよくあります。この場合、モジュール自体は純粋に機能的に動作し、関数が作成された時間は観測できません。しかし、モジュールのトップレベルの式に副作用が含まれている場合は、副作用のタイミングが問題になることがあります。モジュールの宣言とインスタンス化を区別することで、そのタイミングをコントロールすることができます。モジュールビジットの概念は、マクロの実装と効果の相互作用をさらに説明します。
---
## 16.3.1 宣言とインスタンス化
モジュールを宣言しても、モジュール本体の式がすぐに評価されるわけではありません。たとえば、次のように評価します。
```ocaml
> (module number-n racket/base
(provide n)
(define n (random 10))
(printf "picked ~a\n" n))
```
はモジュールnumber-nを宣言していますが、すぐにnに乱数を選んだり、数値を表示したりはしません。number-nをrequireすると、モジュールがインスタンス化され(インスタンス化のトリガーとなり)、モジュール本体の式が評価されることになります。
```
> (require 'number-n)
picked 5
> n
5
```
モジュールが特定の名前空間でインスタンス化された後、そのモジュールのさらなるrequireは、モジュールを再度インスタンス化するのではなく、同じインスタンスを使用します。
```
> (require 'number-n)
> n
5
> (module use-n racket/base
(require 'number-n)
(printf "still ~a\n" n))
> (require 'use-n)
still 5
```
dynamic-require関数は、requireと同様に、モジュールがまだインスタンス化されていない場合に、モジュールのインスタンス化をトリガーします。そのため、モジュールのインスタンス化の効果だけをトリガーするには、第2引数に#fを指定したdynamic-requireが便利です。
```
> (module use-n-again racket/base
(require 'number-n)
(printf "also still ~a\n" n))
> (dynamic-require ''use-n-again #f)
also still 5
```
require によるモジュールのインスタンス化は推移的です。つまり、あるモジュールのrequireがそのモジュールをインスタンス化した場合、そのモジュールにrequireされたモジュールも(すでにインスタンス化されていなければ)インスタンス化されます。
```ocaml
> (module number-m racket/base
(provide m)
(define m (random 10))
(printf "picked ~a\n" m))
> (module use-m racket/base
(require 'number-m)
(printf "still ~a\n" m))
> (require 'use-m)
picked 0
still 0
```
---
## 16.3.2 コンパイル時のインスタンス化
モジュールを宣言しても、それだけではモジュールがインスタンス化されないのと同様に、他のモジュールを必要とするモジュールを宣言しても、それだけでは必要なモジュールがインスタンス化されません。しかし、モジュールを宣言することで、モジュールの展開とコンパイルが行われます。あるモジュールが(require (for-syntax ....))で他のモジュールをインポートしている場合、for-syntaxをインポートしているモジュールは展開時にインスタンス化されなければなりません。
```ocaml
> (module number-p racket/base
(provide p)
(define p (random 10))
(printf "picked ~a\n" p))
> (module use-p-at-compile-time racket/base
(require (for-syntax racket/base
'number-p))
(define-syntax (pm stx)
#`#,p)
(printf "was ~a at compile time\n" (pm)))
picked 1
```
名前空間での実行時のインスタンス化とは異なり、あるモジュールが同じ名前空間内の別のモジュールの展開にfor-syntaxとして使用される場合、for-syntaxされたモジュールは展開ごとに別々にインスタンス化されます。前述の例を続けると、number-pが2回目のfor-syntaxに使用された場合、2番目の乱数が新しいpに選択されます。
```ocaml
> (module use-p-again-at-compile-time racket/base
(require (for-syntax racket/base
'number-p))
(define-syntax (pm stx)
#`#,p)
(printf "was ~a at second compile time\n" (pm)))
picked 3
```
コンパイル時にnumber-pのインスタンスを分離することで、あるモジュールのコンパイルから別のモジュールのコンパイルへの偶発的な影響の伝播を防ぐことができます。このような影響を防ぐことで、コンパイルを確実に分離し、より決定性の高いものにすることができます。
use-p-at-compile-timeおよびuse-p-again-at-compile-timeの拡張形式では、毎回選択された番号が記録されるため、モジュールがインスタンス化されたときに、これら2つの異なる番号が表示されます。
```ocaml
> (dynamic-require ''use-p-at-compile-time #f)
was 1 at compile time
> (dynamic-require ''use-p-again-at-compile-time #f)
was 3 at second compile time
```
名前空間のトップレベルは、独立したモジュールのように振る舞います。トップレベルの複数のインタラクションは、概念的にモジュールの単一の拡張を行います。そのため、トップレベルで(require (for-syntax ....))を2回使用しても、2回目の使用では新しいコンパイル時インスタンスは発生しません。
```ocaml
> (begin (require (for-syntax 'number-p)) 'done)
picked 4
'done
> (begin (require (for-syntax 'number-p)) 'done-again)
'done-again
```
しかし、モジュールの実行時のインスタンスは、トップレベルを含めたすべてのコンパイル時のインスタンスから分離されているので、for-syntaxでないnumber-pの使用は別の乱数を選ぶことになります。
```ocaml
> (require 'number-p)
picked 5
```
---
## 16.3.3 訪問するモジュール
モジュールが他のモジュールで使用するためにマクロを提供する場合、他のモジュールは、for-syntax を使用せずに、マクロ提供者を直接要求してマクロを使用します。これは、for-syntax がコンパイル時の位置で使用するバインディングをインポートするのに対し、マクロは実行時の位置で使用するためにインポートされるからです。
一方、マクロを実装するモジュールは、マクロを実装するために別のモジュールfor-syntaxを必要とする場合があります。for-syntaxモジュールは、マクロを使用する可能性のあるモジュールの拡張時に、コンパイル時のインスタンス化を必要とします。この要件は、インスタンス化の他力本願に似たrequireによる他力本願を設定しますが、for-syntaxのシフトがチェーンの中で発生するポイントでは「1つ違い」になります。
このシナリオを具体化するための例を示します。
```ocaml
> (module number-q racket/base
(provide q)
(define q (random 10))
(printf "picked ~a\n" q))
> (module use-q-at-compile-time racket/base
(require (for-syntax racket/base
'number-q))
(provide qm)
(define-syntax (qm stx)
#`#,q)
(printf "was ~a at compile time\n" (qm)))
picked 7
> (module use-qm racket/base
(require 'use-q-at-compile-time)
(printf "was ~a at second compile time\n" (qm)))
picked 4
> (dynamic-require ''use-qm #f)
was 7 at compile time
was 4 at second compile time
```
この例では、use-q-at-compile-timeを展開してコンパイルすると、number-qが一度インスタンス化されます。この場合、そのインスタンス化は(qm)マクロを展開するために必要ですが、モジュールシステムは、qmマクロが使用されないことが判明した場合でも、number-qのコンパイル時のインスタンス化を積極的に行います。
そして、use-qmが展開されてコンパイルされると、number-qの2番目のコンパイル時インスタンスが作成されます。そのコンパイル時のインスタンス化はuse-qm内の(qm)形式を拡張するために必要です。
use-qmをインスタンス化すると、その2番目のモジュールのコンパイル時に選ばれた数字が正しく報告されます。しかし、最初に、use-qm内のuse-q-at-compile-timeのrequireが、use-q-at-compile-timeの推移的なインスタンス化を引き起こし、そのコンパイル時に選ばれた番号を正しく報告します。
全体として、この例は、すでに見たrequireの推移的効果を示しています。
* モジュールがインスタンス化されると、そのボディのランタイム式が評価されます。
* モジュールがインスタンス化されると、そのモジュールが(for-syntaxなしで)要求するすべてのモジュールもインスタンス化されます。
しかし、このルールはnumber-qのコンパイル時のインスタンス化を説明していません。これを説明するには、「コンパイル時インスタンス化」で見た概念のために、visitという新しい言葉が必要です。
* モジュールが訪問されると、その本体にあるコンパイル時の式(マクロ定義など)が評価されます。
* モジュールが拡張されると、そのモジュールが訪問されます。
* モジュールが訪問されると、そのモジュールが必要とする(for-syntaxのない)モジュールも訪問されます。
* モジュールが訪問されると、そのモジュールが for-syntax を必要とするすべてのモジュールがコンパイル時にインスタンス化されます。
あるモジュールを訪問すると別のモジュールがコンパイル時にインスタンス化される場合、通常のrequireによるインスタンス化の移行性がより多くのコンパイル時のインスタンス化を引き起こす可能性があることに注意してください。しかし、インスタンス化されたモジュールはすでに展開され、コンパイルされているので、インスタンス化自体がさらなる訪問を引き起こすことはありません。
訪問によって評価されるモジュールのコンパイル時の表現には、define-syntaxフォームの右辺とbegin-for-syntaxフォームのボディの両方が含まれます。以下の例で、ランダムに選択された数字がすぐに表示されるのはそのためです。
```ocaml
> (module compile-time-number racket/base
(require (for-syntax racket/base))
(begin-for-syntax
(printf "picked ~a\n" (random)))
(printf "running\n"))
picked 0.25549265186825576
```
このモジュールをインスタンス化すると、実行時の式だけが評価され、"running "と表示されますが、新しい乱数は表示されません。
```ocaml
> (dynamic-require ''compile-time-number #f)
running
```
上記のinstantiatesとvisitの説明は、通常のrequireとfor-syntaxのrequireで表現されていますが、より正確な仕様はモジュールのフェーズで表現されています。例えば、モジュールAが(require (for-syntax B))を持ち、モジュールBが(require (for-template C))を持っている場合、for-syntaxとfor-templateのシフトがキャンセルされるため、モジュールAがインスタンス化されたときにモジュールCがインスタンス化されることになります。for-syntaxが結合したときにfor-meta 2で何が起こるかはまだ規定していません。それは次のセクション「利用可能なモジュールを介した遅延訪問」に任せます。
トップレベルを継続的に拡張される一種のモジュールと考えると、上記のルールは、トップレベルで別のモジュールを要求すると、(それがまだインスタンス化されておらず、かつ訪問もされていない場合)その別のモジュールをインスタンス化し、かつ訪問することを意味します。これはおおよそ正しいのですが、次のセクション「利用可能なモジュールを経由した遅延訪問」でも説明するように、訪問を遅延させます。
一方、 dynamic-require はモジュールをインスタンス化するだけで、モジュールを訪問することはありません。前述の例で require の代わりに dynamic-require が使われているのは、この単純化のためです。トップレベルのrequireの余分な訪問は、以前の例を分かりにくくします。
---
## 16.3.4 利用可能なモジュールを経由した遅延訪問
モジュールのトップレベルのrequireは、実際にはそのモジュールを訪問しません。代わりに、そのモジュールを利用可能にします。利用可能なモジュールは、将来の式が同じ文脈で展開される必要があるときに訪問されます。次の式には、コンパイル時にヘルパーを評価する必要のあるインポートされたマクロが含まれているかもしれませんが、念のため、モジュールシステムは積極的にモジュールを訪問します。
次の例では、モジュールが展開されている間にモジュールの本体を訪問した結果、乱数が選ばれます。モジュールのrequireは、モジュールをインスタンス化し、"running "と表示し、また、モジュールを利用可能にします。他の式を評価するとその式が展開され、その展開が利用可能なモジュールの訪問のきっかけとなり、別の乱数が選ばれます。
```ocaml
> (module another-compile-time-number racket/base
(require (for-syntax racket/base))
(begin-for-syntax
(printf "picked ~a\n" (random)))
(printf "running\n"))
picked 0.3634379786893492
> (require 'another-compile-time-number)
running
> 'next
picked 0.5057086679589476
'next
> 'another
'another
```
<div class="box">エクスパンダは、トップレベルのbeginが発見されると同時に、その内容をトップレベルにフラット化することに注意してください。つまり、(begin (require 'another-compile-time-number) 'next)では、"next "の前に "pick "が表示されたままです。
</div>
<br>
anotherの最終的な評価は、利用可能なモジュールを訪問しますが、'next'を評価しただけで新たに利用可能になったモジュールはありません。
あるモジュールが、1より大きなnに対してfor-meta nを使用する別のモジュールを必要とする場合、必要なモジュールはフェーズnで利用可能になります。フェーズnで利用可能なモジュールは、フェーズn-1のある式が展開されるときに訪問されます。
以下の例では、(variable-reference->module-base-phase (#%variable-reference)) を使用しています。この例では、包含するモジュールがインスタンス化されるフェーズの番号が返されます。
```ocaml
> (module show-phase racket/base
(printf "running at ~a\n"
(variable-reference->module-base-phase (#%variable-reference))))
> (require 'show-phase)
running at 0
> (module use-at-phase-1 racket/base
(require (for-syntax 'show-phase)))
running at 1
> (module unused-at-phase-2 racket/base
(require (for-meta 2 'show-phase)))
```
上記の最後のモジュールでは、show-phaseがフェーズ2で使用可能になりましたが、モジュール内のどの式もフェーズ1で展開されていないため、フェーズ2のプリントアウトはありません。次のモジュールでは、フェーズ2のrequireの後にフェーズ1の式が含まれているため、プリントアウトがあります。
```ocaml
> (module use-at-phase-2 racket/base
(require (for-meta 2 'show-phase)
(for-syntax racket/base))
(define-syntax x 'ok))
running at 2
```
トップレベルでモジュールuse-at-phase-1を要求すると、show-phaseはphase 1で使用できるようになります。別の式を評価すると、use-at-phase-1 が訪問され、それによって show-phase がインスタンス化されます。
```ocaml
> (require 'use-at-phase-1)
> 'next
running at 1
'next
```
use-at-phase-2のrequireも同様ですが、show-phaseはフェーズ2で利用可能になるため、フェーズ1で何らかの式が展開されるまでインスタンス化されません。
```ocaml
> (require 'use-at-phase-2)
> 'next
'next
> (require (for-syntax racket/base))
> (begin-for-syntax 'compile-time-next)
running at 2
```
{"metaMigratedAt":"2023-06-16T11:43:08.978Z","metaMigratedFrom":"YAML","title":"16.3","breaks":true,"slideOptions":"{\"theme\":\"white\",\"slideNumber\":\"c/t\",\"center\":false,\"transition\":\"none\",\"keyboard\":true,\"width\":\"93%\",\"height\":\"300%\"}","contributors":"[{\"id\":\"a0de3d6a-cf71-4961-a3bb-e324e7c21a77\",\"add\":13059,\"del\":658}]"}