Files
Decodal/doc/manual/source/language/operators.md
T

251 lines
7.4 KiB
Markdown

# Operators
This chapter defines the meaning of Decodal's operators.
## Operator reference
| Operator | Form | Kind | Operands | Result / meaning |
|---|---|---|---|---|
| `.` | `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 |
A concrete scalar is a `String`, `Bool`, `Int`, or `Float` value.
## Precedence
Precedence is highest first:
1. Function calls and field references
2. Unary `!` and `-`
3. `*` and `/`
4. `+` and `-`
5. `++`
6. `==`, `!=`, `<`, `<=`, `>`, and `>=`
7. `&&`
8. `||`
9. `&`
10. `//`
11. `default`
12. `as`
Binary operators at the same precedence are left-associative.
`default` is right-associative.
## Arithmetic operators
`+`, `-`, `*`, and `/` perform arithmetic on concrete `Int` and `Float` values.
See [Arithmetic Expression](./expression/arithmetic.md) for details.
## Array concatenation operator
`++` concatenates two concrete arrays.
It does not transform the elements; elements from the right side follow elements from the left.
```dcdl
["read", "write"] ++ ["admin"]
```
## Logical and comparison operators
`!`, `&&`, and `||` operate on concrete `Bool` values.
`&&` and `||` short-circuit.
`==` and `!=` compare concrete scalar values.
`<`, `<=`, `>`, and `>=` compare concrete numeric values.
See [Logical and Comparison Expressions](./expression/logical-and-comparison.md) for details.
## `&`: constraint-preserving composition
`&` composes values, constraints, and structures.
```dcdl
A & B
```
The basic rules are:
- Two constraints become a constraint that satisfies both.
- A constraint and a concrete value require the concrete value to satisfy the constraint.
- Two identical concrete values produce that value.
- Two different concrete values conflict.
- Two objects are composed field by field.
- A field present on both sides is composed with `&`.
- A field present on only one side is retained unchanged.
- Any contradiction produces an error.
For example:
```dcdl
Port = Int & >= 1 & <= 65535;
NarrowedPort = Port & > 443;
MyConfig = {
port = NarrowedPort default 8080;
};
Config = MyConfig & {
port = 8000;
};
```
Conceptually, `Config.port` becomes:
```text
NarrowedPort & 8000
```
This succeeds because `8000` satisfies `NarrowedPort`.
The following fails:
```dcdl
BadConfig = MyConfig & {
port = 80;
};
```
`80` does not satisfy `> 443`.
## `//`: patch composition
`//` is a right-biased structural patch operator.
Where `&` preserves constraints, `//` changes or overrides configuration and schemas.
```dcdl
A // B
```
The basic rules are:
- Two objects are patched recursively field by field.
- The same field containing object/object is patched recursively.
- The same field containing anything other than object/object is replaced by the right side.
- Fields present only on the left are retained.
- Fields present only on the right are added.
- Arrays are replaced by the right side by default.
- Function values are replaced by the right side by default.
Thus, `//` is a deep patch rather than a shallow merge.
```dcdl
Base = {
feature_hoge = {
enable = Bool default true;
fuga = Int default 10;
};
};
Patched = Base // {
feature_hoge = {
enable = false;
};
};
```
`Patched` is equivalent to:
```dcdl
{
feature_hoge = {
enable = false;
fuga = Int default 10;
};
}
```
The same patch can be written with a dot path:
```dcdl
Patched = Base // {
feature_hoge.enable = false;
};
```
## Replacing an entire object
Under `//`, object/object is always patched deeply.
The core language does not include a special `replace(...)` syntax or built-in function for replacing an entire object field.
To use an entirely different object structure, construct the value outside the object being patched.
## `as`: range refinement
`as` is an asymmetric refinement operator that verifies the left side is more concrete and narrower than the right side.
```dcdl
Refined = {
port = 8000;
} as {
port = Int & >= 1 & <= 65535;
host = String;
enabled = Bool default true;
};
```
`port` becomes the concrete value `8000`.
`host` and `enabled`, which exist only on the right, remain abstract in the result. Whether a field has a default does not affect preservation of a right-only field.
`as` does not select defaults; normal default rules apply only if the result is later materialized.
A field present only in the left object lies outside the right-hand field domain and produces an error.
Nested objects, array constraints, and map constraints present on both sides are refined recursively using the same rules.
Containment can also be checked between abstract ranges.
```dcdl
Int & > 10 as Int & > 0 # succeeds and returns Int & > 10
Int as Int & > 0 # fails
```
`&` is symmetric and retains object fields found on only one side.
Do not use it in place of directional refinement.
See [Range Refinement](./expression/ascription.md) for details.
## Choosing between `&`, `//`, and `as`
Use `&` for symmetric composition that preserves constraints and partial structure.
```dcdl
Combined = MyConfig & {
port = 8000;
};
```
Use `//` to override or reshape an existing structure.
```dcdl
ModifiedSchema = MyConfig // {
port = Int default 9000;
};
```
Because `//` is a right-biased patch, it does not always preserve constraints from the left.
Use `&` when those constraints must remain.
Use `as` to compose while verifying that the left side is narrower than the right.
```dcdl
RefinedConfig = NarrowConfig as WideConfig;
```