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 同士の合成は conflict とする。
同じ default は同一候補として扱ってよい。
`&` で片方だけが default を持つ場合、その default を保持する。
同じ thunk 由来の default 同士は同一 default として保持する。
異なる default 同士は conflict とする。
```text
merge_default(None, None) -> None

View File

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

View File

@ -188,11 +188,12 @@ RuntimeValue =
## default の合成
同じフィールドに複数の `default` が合成された場合の詳細規則は未確定である。
現時点の単純な方針は以下である。
`default` の合成規則は以下である。
- `&` による default 同士の衝突はエラーにする。
- 同一 default 値は許可してよい。
- `//` による patch では右辺 default が左辺 default を置き換える。
- `&` で片方だけが default を持つ場合、その default を保持する。
- `&` で両方が異なる default を持つ場合、conflict になる。
- `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 として渡せる。
関数本体内で引数が参照されたときに評価する。
任意の関数呼び出し結果をグローバルに memoize することは必須ではない。
関数呼び出し結果そのものはグローバルには memoize しない。
ただし、フィールドに束縛された呼び出し結果は、そのフィールド thunk の評価結果として memoize される。

View File

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

View File

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

View File

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

View File

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

View File

@ -12,4 +12,5 @@ config.feature_hoge.enable
左側の式を object として評価し、指定された 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
"${part.greet}! ${part.target}"
```
Decodal の string literal は、現時点では literal text として扱う。
式を埋め込む構文は定義しない。
## 評価
補間式の評価タイミングは通常の遅延評価に従う。
文字列補間を初期実装に含めるかは未確定である。
必要になった場合は、文字列連結や明示的な formatting function として別途設計する。

View File

@ -5,54 +5,47 @@
## 構文
```dcdl
(part: {
greet = String;
target = String;
}) =>
"${part.greet}! ${part.target}"
(value: Int) => value + 1
```
関数呼び出しは通常の呼び出し構文で行う。
```dcdl
mk_hw({
greet = "Hello";
target = "World";
})
let
increment = (value: Int) => value + 1;
in
increment(41)
```
複数引数の構文は候補として以下を想定する。
複数引数も指定できる。
```dcdl
(
input_a: {
hoge = Int & >= 0;
},
input_b: {
fuga = 20;
}
) =>
(input_a: { hoge = Int & >= 0; }, input_b: { fuga = Int; }) =>
{
# ...
hoge = input_a.hoge;
fuga = input_b.fuga;
}
```
## 関数の意味
関数は値として扱える。
ただし、最終データとして関数値を出力できるかどうかは別途定める。
設定ファイルを materialize する段階では、未適用の関数値は出力不能な値として扱うのが自然である。
関数は runtime value として扱えるが、最終データとして materialize することはできない。
未適用の関数値が materialize 対象に残っている場合は diagnostic になる。
関数値は opaque であり、関数値同士の等価性は提供しない。
`&` で関数値同士を合成すると conflict になる。
## 評価方針
関数は以下の方針を基本とする。
関数は以下の方針で評価する。
- 関数は純粋である。
- 関数はレキシカルスコープを持つ。
- 関数は定義時の環境を参照として保持する。
- 引数は必要になるまで評価しない。
- フィールドに束縛された関数呼び出し結果は、そのフィールド評価結果として memoize される。
- 任意の関数呼び出しそのものをグローバルに memoize することは必須ではない。
- 引数は thunk として渡され、必要になるまで評価されない。
- 関数呼び出し結果そのものはグローバルには memoize しない。
- フィールドに束縛された関数呼び出し結果は、そのフィールド thunk の評価結果として memoize される。
- 再帰的な依存は thunk cycle として diagnostic になる。
関数値の内部モデル例:
@ -63,29 +56,3 @@ Function {
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 には含めない。
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` を参照する。
通常の object literal 内の field を暗黙に recursive scope にするかは別仕様とする。
通常の object literal 内の field は、その object 内の sibling field を暗黙には識別子として参照できない。
object 内の値を参照する場合は、外側で束縛された値や明示的な path reference を使う。
## SourceLoader

View File

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

View File

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

View File

@ -9,7 +9,10 @@ ratio = Float;
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! .*/;
```
正規表現制約を必須機能にするかは未確定である。
正規表現制約の検証は Rust crate の `regex` feature で有効化される。
feature が無効な場合、正規表現制約は具体 string に対して検証できず diagnostic になる。

View File

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