# Thunk and Lazy Evaluation thunk は、まだ評価していない式をあとで評価できるように包んだ遅延計算である。 この処理系では、循環検出と memoize の単位として thunk を使う。 ## Thunk ```text Thunk { expr: ExprRef env: EnvId state: ThunkState } ExprRef { module: ModuleId expr: ExprId } ThunkState = Unevaluated Evaluating Evaluated(RuntimeValue) Error(Diagnostic) ``` `expr` は評価対象の AST node を module-qualified に指す。 `ExprId` は module-local な ID なので、runtime では `ModuleId` と組み合わせた `ExprRef` を保持する。 `env` は、その式を評価するときに使う lexical environment を指す。 式だけではなく environment も保持するのは、遅延評価された式が定義時の名前解決文脈を必要とするためである。 ## force thunk を評価する操作を force と呼ぶ。 ```text force(thunk): Unevaluated -> Evaluating -> Evaluated(value) Evaluated(value) -> value Evaluating -> cycle diagnostic Error(diagnostic) -> diagnostic ``` 一度 `Evaluated` になった thunk は memoize される。 同じ thunk を複数回 force しても、式は一度だけ評価される。 ## 循環検出 評価中の thunk を再度 force しようとした場合は循環依存である。 ```dcdl { a = b; b = a; } ``` `a` を force すると、`a -> b -> a` と戻る。 このとき `a` は `Evaluating` なので cycle diagnostic を返す。 ## 遅延評価の単位 thunk は主に以下に使う。 - module root - object field - let binding - function argument - default expression object は field ごとに thunk を持つ。 そのため、object の一部だけが必要な場合、他の field は評価されない。 ## module import import は module を登録するが、module 全体を即時評価しない。 module root や field は thunk として保持され、参照されたときだけ force される。 これにより、module 間に循環 import があっても、force された thunk の依存が循環しなければ評価できる。 ## function call 関数引数は thunk として関数の environment に束縛する。 関数本体で引数が参照されたときだけ force する。 任意の関数呼び出し結果をグローバルに memoize する必要はない。 field に束縛された関数呼び出し結果は、その field thunk の評価結果として memoize される。