Decodal/doc/manual/souce/design/thunk-and-lazy-evaluation.md

2.5 KiB

Thunk and Lazy Evaluation

thunk は、まだ評価していない式をあとで評価できるように包んだ遅延計算である。 この処理系では、循環検出と memoize の単位として thunk を使う。

Thunk

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 と呼ぶ。

force(thunk):
  Unevaluated -> Evaluating -> Evaluated(value)
  Evaluated(value) -> value
  Evaluating -> cycle diagnostic
  Error(diagnostic) -> diagnostic

一度 Evaluated になった thunk は memoize される。 同じ thunk を複数回 force しても、式は一度だけ評価される。

循環検出

評価中の thunk を再度 force しようとした場合は循環依存である。

{
    a = b;
    b = a;
}

a を force すると、a -> b -> a と戻る。 このとき aEvaluating なので 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 される。