# 演算子 この章では、Decodal の演算子の意味を定義する。 ## 演算子一覧 | 演算子 | 形 | 種類 | 対象 | 結果 / 意味 | |---|---|---|---|---| | `.` | `object.field` | field reference | object / abstract object | field value | | call | `fn(arg)` | function call | function | function result | | `!` | `!expr` | unary logical | concrete `Bool` | concrete `Bool` | | `-` | `-expr` | unary arithmetic | concrete `Int` / `Float` | negated number | | `*` | `lhs * rhs` | arithmetic | concrete `Int` / `Float` | numeric product | | `/` | `lhs / rhs` | arithmetic | concrete `Int` / `Float` | `Float` quotient | | `+` | `lhs + rhs` | arithmetic | concrete `Int` / `Float` | numeric sum | | `-` | `lhs - rhs` | arithmetic | concrete `Int` / `Float` | numeric difference | | `++` | `lhs ++ rhs` | array concat | concrete arrays | concatenated array | | `==` | `lhs == rhs` | equality | concrete scalar | concrete `Bool` | | `!=` | `lhs != rhs` | equality | concrete scalar | concrete `Bool` | | `<` | `lhs < rhs` | ordering | concrete `Int` / `Float` | concrete `Bool` | | `<=` | `lhs <= rhs` | ordering | concrete `Int` / `Float` | concrete `Bool` | | `>` | `lhs > rhs` | ordering | concrete `Int` / `Float` | concrete `Bool` | | `>=` | `lhs >= rhs` | ordering | concrete `Int` / `Float` | concrete `Bool` | | `>` | `> value` | comparison constraint | numeric constraint value | abstract constraint | | `>=` | `>= value` | comparison constraint | numeric constraint value | abstract constraint | | `<` | `< value` | comparison constraint | numeric constraint value | abstract constraint | | `<=` | `<= value` | comparison constraint | numeric constraint value | abstract constraint | | `&&` | `lhs && rhs` | logical | concrete `Bool` | short-circuit AND | | `||` | `lhs || rhs` | logical | concrete `Bool` | short-circuit OR | | `&` | `lhs & rhs` | composition | value / constraint / object | constraint-preserving composition | | `//` | `lhs // rhs` | patch | object / value | right-biased structural patch | | `default` | `base default fallback` | default | abstract value | materialization fallback | | `as` | `narrower as wider` | range refinement | value / constraint / structure | narrower result plus untouched right-only ranges | `concrete scalar` は `String`、`Bool`、`Int`、`Float` を指す。 ## 優先順位 優先順位は高い順に以下である。 1. 関数呼び出しとフィールド参照 2. unary `!` `-` 3. `*` `/` 4. `+` `-` 5. `++` 6. `==` `!=` `<` `<=` `>` `>=` 7. `&&` 8. `||` 9. `&` 10. `//` 11. `default` 12. `as` 同じ優先順位の二項演算子は左結合である。 `default` は右結合である。 ## Arithmetic operators `+` `-` `*` `/` は具体的な `Int` / `Float` に対する四則演算である。 詳しくは [Arithmetic Expression](./expression/arithmetic.md) を参照する。 ## Array concat operator `++` は concrete array 同士を連結する演算子である。 要素は変換されず、左辺の要素の後に右辺の要素が並ぶ。 ```dcdl ["read", "write"] ++ ["admin"] ``` ## Logical and comparison operators `!` `&&` `||` は concrete `Bool` に対する論理演算である。 `&&` と `||` は短絡評価される。 `==` `!=` は concrete scalar value を比較する。 `<` `<=` `>` `>=` は concrete numeric value を比較する。 詳しくは [Logical and Comparison Expressions](./expression/logical-and-comparison.md) を参照する。 ## `&`: 制約合成 `&` は値・制約・構造を合成する演算子である。 ```dcdl A & B ``` 基本規則: - 両方が制約なら、両方を満たす制約になる。 - 制約と具体値なら、具体値が制約を満たす必要がある。 - 両方が同じ具体値なら、その値になる。 - 両方が異なる具体値なら conflict になる。 - 両方が object なら、フィールドごとに合成する。 - 同じフィールドが両方にある場合、そのフィールド値を `&` で合成する。 - 片方にしかないフィールドは、そのまま結果に保持する。 - 矛盾が発生した場合はエラーになる。 例: ```dcdl Port = Int & >= 1 & <= 65535; NarrowedPort = Port & > 443; MyConfig = { port = NarrowedPort default 8080; }; Config = MyConfig & { port = 8000; }; ``` `Config.port` は概念的には以下になる。 ```text NarrowedPort & 8000 ``` `8000` は `NarrowedPort` を満たすため成功する。 一方、以下は失敗する。 ```dcdl BadConfig = MyConfig & { port = 80; }; ``` `80` は `> 443` を満たさないためである。 ## `//`: patch 合成 `//` は右辺優先の構造的 patch 演算子である。 `&` が制約を保った合成であるのに対し、`//` は設定やスキーマを上書き・変更するために使う。 ```dcdl A // B ``` 基本規則: - 両方が object なら、フィールドごとに再帰的に patch する。 - 同じフィールドが object/object なら、さらに再帰的に patch する。 - 同じフィールドが object/object 以外なら、右辺で置き換える。 - 左辺にしかないフィールドは保持する。 - 右辺にしかないフィールドは追加する。 - 配列はデフォルトでは右辺で置き換える。 - 関数値はデフォルトでは右辺で置き換える。 つまり `//` は shallow merge ではなく deep patch とする。 ```dcdl Base = { feature_hoge = { enable = Bool default true; fuga = Int default 10; }; }; Patched = Base // { feature_hoge = { enable = false; }; }; ``` `Patched` は以下に相当する。 ```dcdl { feature_hoge = { enable = false; fuga = Int default 10; }; } ``` ドットパスを使うと以下のようにも書ける。 ```dcdl Patched = Base // { feature_hoge.enable = false; }; ``` ## object 全体の置換 `//` では object/object は常に deep patch される。 object field 全体を特別に置き換えるための `replace(...)` 構文や組み込み関数は core には含めない。 object 全体を別構造にしたい場合は、patch 対象より外側で値を作り直す。 ## `as`: range refinement `as` は左辺が右辺より具体的で狭い範囲であることを確認する、非対称な絞り込み演算子である。 ```dcdl Refined = { port = 8000; } as { port = Int & >= 1 & <= 65535; host = String; enabled = Bool default true; }; ``` `port` は左辺の `8000` に具体化される。 右辺にしかない `host` と `enabled` は abstract のまま結果へ残る。default の有無は、右辺だけの field を保持するかどうかに影響しない。 `as` 自体は default を選択せず、後から結果を materialize した場合だけ通常の default 規則が働く。 左辺にしかない object field は右辺の field domain 外なのでエラーになる。 両側にある nested object、array constraint、map constraint は同じ規則で再帰的に絞り込む。 abstract range 同士も包含を確認できる。 ```dcdl Int & > 10 as Int & > 0 # 成功し、Int & > 10 を返す Int as Int & > 0 # 失敗 ``` `&` は対称な合成であり、片方だけにある object field を保持する。 したがって、左右に方向を持つ絞り込みを `&` で代用しない。 詳細は [Range Refinement](./expression/ascription.md) を参照する。 ## `&`、`//`、`as` の使い分け `&` は制約や部分構造を失わず、対称に合成する。 ```dcdl Combined = MyConfig & { port = 8000; }; ``` `//` は既存構造の上書きや変形に使う。 ```dcdl ModifiedSchema = MyConfig // { port = Int default 9000; }; ``` `//` は右辺優先の patch であり、左辺の制約を常に保持するとは限らない。 制約を保持したい場合は `&` を使う。 左辺が右辺より狭いことを検証しながら合成する場合は `as` を使う。 ```dcdl RefinedConfig = NarrowConfig as WideConfig; ```