Trim open issues to materialization

This commit is contained in:
Keisuke Hirata 2026-07-09 02:46:28 +09:00
parent 8e1cb01f52
commit 6d80c66144
No known key found for this signature in database
18 changed files with 92 additions and 187 deletions

View File

@ -59,8 +59,9 @@ Config = MyConfig & {
## default の合成 ## default の合成
初期方針では、`&` による異なる default 同士の合成は conflict とする。 `&` で片方だけが default を持つ場合、その default を保持する。
同じ default は同一候補として扱ってよい。 同じ thunk 由来の default 同士は同一 default として保持する。
異なる default 同士は conflict とする。
```text ```text
merge_default(None, None) -> None merge_default(None, None) -> None

View File

@ -84,5 +84,5 @@ module root や field は thunk として保持され、参照されたときだ
関数引数は thunk として関数の environment に束縛する。 関数引数は thunk として関数の environment に束縛する。
関数本体で引数が参照されたときだけ force する。 関数本体で引数が参照されたときだけ force する。
任意の関数呼び出し結果をグローバルに memoize する必要はない。 関数呼び出し結果そのものはグローバルには memoize しない。
field に束縛された関数呼び出し結果は、その field thunk の評価結果として memoize される。 field に束縛された関数呼び出し結果は、その field thunk の評価結果として memoize される。

View File

@ -188,11 +188,12 @@ RuntimeValue =
## default の合成 ## default の合成
同じフィールドに複数の `default` が合成された場合の詳細規則は未確定である。 `default` の合成規則は以下である。
現時点の単純な方針は以下である。
- `&` による default 同士の衝突はエラーにする。 - `&` で片方だけが default を持つ場合、その default を保持する。
- 同一 default 値は許可してよい。 - `&` で両方が異なる default を持つ場合、conflict になる。
- `//` による patch では右辺 default が左辺 default を置き換える。 - `Abstract & Concrete` が成功した場合、結果は concrete value になり default は消える。
- `//` では右辺が左辺を置き換える。object/object の場合は field ごとに再帰 patch されるため、右辺 field の default が左辺 field の default を置き換える。
この方針により、`&` は制約を保った合成、`//` は上書き操作として説明できる。 `default` thunk は materialize 時に必要になった時点で評価する。
評価された default value は、同じ abstract value に残っている制約を満たす必要がある。

View File

@ -74,5 +74,5 @@ Error 評価失敗
関数引数は thunk として渡せる。 関数引数は thunk として渡せる。
関数本体内で引数が参照されたときに評価する。 関数本体内で引数が参照されたときに評価する。
任意の関数呼び出し結果をグローバルに memoize することは必須ではない。 関数呼び出し結果そのものはグローバルには memoize しない。
ただし、フィールドに束縛された呼び出し結果は、そのフィールド thunk の評価結果として memoize される。 ただし、フィールドに束縛された呼び出し結果は、そのフィールド thunk の評価結果として memoize される。

View File

@ -5,7 +5,6 @@
## 基本的な設定スキーマ ## 基本的な設定スキーマ
```dcdl ```dcdl
rec {
Host = IPv4Address; Host = IPv4Address;
Port = Int & >= 1 & <= 65535; Port = Int & >= 1 & <= 65535;
@ -19,7 +18,6 @@ rec {
fuga = Int default 10; fuga = Int default 10;
}; };
}; };
}
NewConfig = MyConfig & { NewConfig = MyConfig & {
host = "127.0.0.1"; host = "127.0.0.1";
@ -33,28 +31,20 @@ disabled_config = NewConfig & {
enabled_config = NewConfig; enabled_config = NewConfig;
``` ```
## 関数と文字列生成 ## 関数と制約
```dcdl ```dcdl
let let
maybe_hw = String & /Hello! .*/; Port = Int & >= 1 & <= 65535;
part = { add_offset = (base: Port, offset: Int) => base + offset;
greet = String;
target = String;
};
mk_hw = (part: part) =>
maybe_hw & "${part.greet}! ${part.target}";
in in
mk_hw({ add_offset(8000, 80)
greet = "Hello";
target = "World";
})
``` ```
評価結果: 評価結果:
```text ```text
"Hello! World" 8080
``` ```
## match ## match

View File

@ -3,10 +3,7 @@
function call expression は、関数値を引数に適用する式である。 function call expression は、関数値を引数に適用する式である。
```dcdl ```dcdl
mk_hw({ increment(41)
greet = "Hello";
target = "World";
})
``` ```
## 評価 ## 評価
@ -14,4 +11,5 @@ mk_hw({
引数は thunk として渡せる。 引数は thunk として渡せる。
関数本体内で引数が参照されたときに評価する。 関数本体内で引数が参照されたときに評価する。
任意の関数呼び出し結果をグローバルに memoize することは必須ではない。 関数呼び出し結果そのものはグローバルには memoize しない。
フィールドに束縛された呼び出し結果は、そのフィールド thunk の評価結果として memoize される。

View File

@ -3,11 +3,7 @@
function expression は、引数を受け取り式を返す値である。 function expression は、引数を受け取り式を返す値である。
```dcdl ```dcdl
(part: { (value: Int) => value + 1
greet = String;
target = String;
}) =>
"${part.greet}! ${part.target}"
``` ```
関数仕様の詳細は [関数](../functions.md) に置く。 関数仕様の詳細は [関数](../functions.md) に置く。

View File

@ -4,12 +4,10 @@ let expression は、ローカル束縛を作る。
```dcdl ```dcdl
let let
part = { base = 8000;
greet = "Hello"; offset = 80;
target = "World";
};
in in
"${part.greet}! ${part.target}" base + offset
``` ```
## 評価 ## 評価

View File

@ -12,4 +12,5 @@ config.feature_hoge.enable
左側の式を object として評価し、指定された field を参照する。 左側の式を object として評価し、指定された field を参照する。
参照先 field は必要になるまで評価されない。 参照先 field は必要になるまで評価されない。
存在しない field への参照をエラーにするか、open schema として扱うかは未確定である。 存在しない field への参照は diagnostic になる。
Decodal は unknown / Any のような値を伝播せず、field の有無を曖昧にしない。

View File

@ -1,12 +1,8 @@
# String Interpolation Expression # String Interpolation
string interpolation は、文字列内に式を埋め込む候補機能である 文字列補間は初期実装には含めない
```dcdl Decodal の string literal は、現時点では literal text として扱う。
"${part.greet}! ${part.target}" 式を埋め込む構文は定義しない。
```
## 評価 必要になった場合は、文字列連結や明示的な formatting function として別途設計する。
補間式の評価タイミングは通常の遅延評価に従う。
文字列補間を初期実装に含めるかは未確定である。

View File

@ -5,54 +5,47 @@
## 構文 ## 構文
```dcdl ```dcdl
(part: { (value: Int) => value + 1
greet = String;
target = String;
}) =>
"${part.greet}! ${part.target}"
``` ```
関数呼び出しは通常の呼び出し構文で行う。 関数呼び出しは通常の呼び出し構文で行う。
```dcdl ```dcdl
mk_hw({ let
greet = "Hello"; increment = (value: Int) => value + 1;
target = "World"; in
}) increment(41)
``` ```
複数引数の構文は候補として以下を想定する。 複数引数も指定できる。
```dcdl ```dcdl
( (input_a: { hoge = Int & >= 0; }, input_b: { fuga = Int; }) =>
input_a: {
hoge = Int & >= 0;
},
input_b: {
fuga = 20;
}
) =>
{ {
# ... hoge = input_a.hoge;
fuga = input_b.fuga;
} }
``` ```
## 関数の意味 ## 関数の意味
関数は値として扱える。 関数は runtime value として扱えるが、最終データとして materialize することはできない。
ただし、最終データとして関数値を出力できるかどうかは別途定める。 未適用の関数値が materialize 対象に残っている場合は diagnostic になる。
設定ファイルを materialize する段階では、未適用の関数値は出力不能な値として扱うのが自然である。
関数値は opaque であり、関数値同士の等価性は提供しない。
`&` で関数値同士を合成すると conflict になる。
## 評価方針 ## 評価方針
関数は以下の方針を基本とする。 関数は以下の方針で評価する。
- 関数は純粋である。 - 関数は純粋である。
- 関数はレキシカルスコープを持つ。 - 関数はレキシカルスコープを持つ。
- 関数は定義時の環境を参照として保持する。 - 関数は定義時の環境を参照として保持する。
- 引数は必要になるまで評価しない。 - 引数は thunk として渡され、必要になるまで評価されない。
- フィールドに束縛された関数呼び出し結果は、そのフィールド評価結果として memoize される。 - 関数呼び出し結果そのものはグローバルには memoize しない。
- 任意の関数呼び出しそのものをグローバルに memoize することは必須ではない。 - フィールドに束縛された関数呼び出し結果は、そのフィールド thunk の評価結果として memoize される。
- 再帰的な依存は thunk cycle として diagnostic になる。
関数値の内部モデル例: 関数値の内部モデル例:
@ -63,29 +56,3 @@ Function {
env: EnvRef env: EnvRef
} }
``` ```
## 関数と重さ
関数の文法・パーサー自体は大きくない。
実装上の重さは、主に評価モデルと意味論から発生する。
注意点:
- クロージャが環境を掴む。
- 引数を lazy にするか strict にするかを決める必要がある。
- 関数呼び出し結果をどこまで memoize するかを決める必要がある。
- 再帰関数を許可するかを決める必要がある。
- 関数値同士の `&` をどう扱うかを決める必要がある。
- 関数値を object に入れたとき、materialize 可能かを決める必要がある。
- import 循環と関数適用が絡んだときの cycle detection が必要になる。
軽量に保つため、初期仕様では以下の制限を検討できる。
- 関数は pure。
- lexical closure は許可。
- 引数は thunk として渡す。
- 関数本体は必要になるまで評価しない。
- 関数値は opaque。
- 関数同士の `&` は、同一関数参照以外は conflict。
- 関数値は最終データとして出力できない。
- 再帰は cycle error として扱う、または v1 では禁止する。

View File

@ -95,4 +95,5 @@ match value {
通常の式はエラー内容に基づいて分岐できない。 通常の式はエラー内容に基づいて分岐できない。
汎用 `try / catch` は core には含めない。 汎用 `try / catch` は core には含めない。
fallback は `default`、`match`、および将来的な optional import / optional field access のような限定された仕組みで表現する。 fallback は `default``match` で表現する。
optional import や optional field access は core には含めない。

View File

@ -29,7 +29,8 @@ result = schema;
``` ```
この場合、`result` の右辺の `schema` は同じ module の top-level field `schema` を参照する。 この場合、`result` の右辺の `schema` は同じ module の top-level field `schema` を参照する。
通常の object literal 内の field を暗黙に recursive scope にするかは別仕様とする。 通常の object literal 内の field は、その object 内の sibling field を暗黙には識別子として参照できない。
object 内の値を参照する場合は、外側で束縛された値や明示的な path reference を使う。
## SourceLoader ## SourceLoader

View File

@ -183,19 +183,10 @@ Patched = Base // {
## object 全体の置換 ## object 全体の置換
`//` が deep patch である場合、object 全体を置き換えたいときの escape hatch が必要になる。 `//` では object/object は常に deep patch される。
object field 全体を特別に置き換えるための `replace(...)` 構文や組み込み関数は core には含めない。
候補として、`replace(...)` を組み込み関数として提供する。 object 全体を別構造にしたい場合は、patch 対象より外側で値を作り直す。
```dcdl
Replaced = Base // {
feature_hoge = replace({
enable = false;
});
};
```
この場合、`feature_hoge` は再帰 patch されず、右辺の object に丸ごと置き換わる。
## `&``//` の使い分け ## `&``//` の使い分け

View File

@ -1,7 +1,7 @@
# 構文と字句 # 構文と字句
この章では、表層構文の方針をまとめる。 この章では、表層構文の方針をまとめる。
厳密な EBNF は未確定であり、今後このファイルに詳細化する。 厳密な構文は [Grammar](./grammar.md) の EBNF を参照する。
## 言語名と拡張子 ## 言語名と拡張子
@ -93,9 +93,9 @@ config.feature_hoge.enable
} }
``` ```
## 予約語候補 ## 予約語
以下は予約語または予約構文として扱う候補である 以下は予約語として扱う。
```text ```text
let let
@ -105,11 +105,8 @@ import
default default
true true
false false
rec
``` ```
`rec` の扱いは未確定である。
## 演算子 ## 演算子
主要な演算子は以下である。 主要な演算子は以下である。

View File

@ -9,7 +9,10 @@ ratio = Float;
threshold = Float default 0.5; threshold = Float default 0.5;
``` ```
## 未確定事項 ## Int との関係
`Int``Float` の暗黙変換を許可するかは未確定である。 `Int``Float` は primitive type constraint としては別の型である。
軽量実装では、両者を明確に分ける方が単純である。 `Float` constraint は concrete `Float` を要求し、`Int` constraint は concrete `Int` を要求する。
数値演算や比較式では `Int``Float` を同じ numeric value として扱える。
混在した四則演算の結果は `Float` になる。

View File

@ -17,4 +17,5 @@ greeting = String default "hello";
message = String & /Hello! .*/; message = String & /Hello! .*/;
``` ```
正規表現制約を必須機能にするかは未確定である。 正規表現制約の検証は Rust crate の `regex` feature で有効化される。
feature が無効な場合、正規表現制約は具体 string に対して検証できず diagnostic になる。

View File

@ -1,60 +1,23 @@
# 未確定事項 # 未確定事項
今後決める必要がある事項を管理する。 今後決める必要がある事項を管理する。
詳細化するときは、各項目を該当する仕様ファイルへ移動または反映する 実装または仕様方針が固まった項目は、該当する仕様ファイルへ反映してここから外す
## 構文 ## Materialize target and host projection
- 正式な字句・構文仕様。 Decodal の評価結果は、制約・default・関数値を含む中間値になり得る。
- 演算子の優先順位。 そのため、Decodal 単独で常に「最終成果物」を一意に決めるのではなく、host 側が期待する型や出力形式を与えて materialize / decode する経路を明確にする必要がある。
- `rec` の扱い。
- コメント構文を `#` のみにするか。
## 型・制約 決めること:
- 配列要素の制約表現 - Rust API で評価結果の field/path を選択して decode / materialize できるようにするか
- object の open/closed schema の扱い - `decodal-derive` の struct schema と評価結果を合成して decode する経路を、主要な materialize path として位置づけるか
- 正規表現を必須機能にするか optional feature にするか。 - CLI / WASM では target path を指定して JSON-compatible value へ materialize する形にするか。
- 代表的な組み込み述語の範囲 - 制約や関数値が残った値を出力したい場合、materialize ではなく inspect/debug API として分けるか
## default 現時点の案:
- `default` 同士の conflict 解決規則。 - Rust では `evaluate -> select field/path -> expected schema と合成 -> decode` を主経路にする。
- `&` による default 合成の厳密な規則。 - CLI / WASM では、明示された target path または module 全体を JSON-compatible value として materialize する。
- `//` による default 置換の厳密な規則。 - materialize できない unresolved abstract value、default のない制約値、関数値は diagnostic にする。
- default thunk の評価失敗をどの段階で報告するか。 - Decodal 言語内には materialize 構文を追加せず、host API / CLI / WASM の責務として扱う。
## 演算子
- `//` による制約・default の置換詳細。
- `replace(...)` を採用するか、別構文を設けるか。
- 配列に対する patch 操作を右辺置換だけにするか。
- 配列 append / prepend / remove などを提供するか。
## 関数
- 関数値の最終出力可否。
- 再帰関数を許可するか。
- 関数同士の `&` の扱い。
- 関数値の等価性。
- 関数呼び出し結果の memoize 範囲。
## 評価
- thunk のエラー memoize 方針。
- import cache の単位。
- 循環 import の診断メッセージ。
- materialize 対象の範囲指定方法。
## match
- match の網羅性チェックを行うか。
- 到達不能分岐を警告するか。
- パターン構文の範囲。
## エラー処理
- optional import を導入するか。
- optional field access を導入するか。
- optional fallback が捕捉できる失敗の範囲。
- エラー報告に制約由来の説明をどこまで含めるか。