Decodal

Composition and Materialization

&//default、materialize は、runtime value の variant に基づいて処理する。

&

& は制約を保った合成である。

compose_and(a: RuntimeValue, b: RuntimeValue) -> RuntimeValue | Diagnostic

基本規則:

Abstract(a) & Abstract(b)
  -> Abstract {
       constraints: a.constraints + b.constraints,
       default: merge_default(a.default, b.default)
     }

Abstract(a) & Concrete(v)
  -> if satisfies(v, a.constraints) then Concrete(v)
     else constraint diagnostic

Concrete(v) & Abstract(a)
  -> if satisfies(v, a.constraints) then Concrete(v)
     else constraint diagnostic

Concrete(Object(a)) & Concrete(Object(b))
  -> Concrete(Object(fieldwise_and(a, b)))

Concrete(a) & Concrete(b)
  -> if a == b then Concrete(a)
     else conflict diagnostic

Abstract & Concrete が成功した場合、default は消える。 明示値があるなら fallback は不要だからである。

object の合成

object は concrete structure だが、field の値は thunk 経由で concrete / abstract のどちらにもなりうる。 object 同士の & は field ごとに再帰合成する。

MyConfig = {
    host = String;
    port = Int default 8080;
};

Config = MyConfig & {
    host = "localhost";
};

hostAbstract(String) & Concrete("localhost") として検証され、成功すれば Concrete("localhost") になる。 port は右辺に明示値がないため、Abstract(Int, default 8080) のまま残る。

default の合成

初期方針では、& による異なる default 同士の合成は conflict とする。 同じ default は同一候補として扱ってよい。

merge_default(None, None) -> None
merge_default(Some(a), None) -> Some(a)
merge_default(None, Some(b)) -> Some(b)
merge_default(Some(a), Some(b)) -> if same(a, b) then Some(a) else conflict

// では右辺 default が左辺 default を置き換える。

//

// は右辺優先の deep patch である。

patch(a: RuntimeValue, b: RuntimeValue) -> RuntimeValue

基本規則:

  • object / object は field ごとに再帰 patch する。
  • object / object 以外は右辺で置き換える。
  • 左辺にしかない field は保持する。
  • 右辺にしかない field は追加する。
  • 配列、scalar、function は右辺置換とする。

// は制約を保持するための演算子ではない。 制約を満たす具体化には & を使う。

materialize

materialize は runtime value を出力可能な Data に変換する。

materialize(RuntimeValue) -> Data | Diagnostic

処理規則:

Concrete(String/Int/Float/Bool) -> Data
Concrete(Array(items))          -> each item を force して materialize
Concrete(Object(fields))        -> each field を force して materialize
Concrete(Function)              -> materialize 不能

Abstract { constraints, default: Some(d) }
  -> force(d)
  -> result が constraints を満たすか検証
  -> materialize(result)

Abstract { constraints, default: None }
  -> 未解決 abstract value として diagnostic

materialize は default を採用する唯一の段階である。 通常評価中に明示値が得られた場合、default は採用されない。

Diagnostic context

Composition and materialization keep source spans for object fields, constraints, defaults, and thunks where possible. When a conflict occurs, the diagnostic should identify the operation span and related participant spans, such as the left field, right field, constraint, or default value. For object materialization errors, diagnostics also include the field path being processed when known.