mirror of
https://github.com/NixOS/nix.git
synced 2025-12-08 18:11:02 +01:00
doc: Document "evaluation order", some strictness, equality quirk
Correct and clarify evaluation semantics including to help users understand Nix language behavior without unnecessarily pinning down the implementation.
This commit is contained in:
parent
42d7d9676d
commit
1039b6719b
2 changed files with 122 additions and 5 deletions
|
|
@ -74,4 +74,48 @@ in f { x = throw "error"; y = throw "error"; }
|
||||||
=> "ok"
|
=> "ok"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Evaluation order
|
||||||
|
|
||||||
|
The order in which expressions are evaluated is generally unspecified, because it does not affect successful evaluation outcomes.
|
||||||
|
This allows more freedom for the evaluator to evolve and to evaluate efficiently.
|
||||||
|
|
||||||
|
Data dependencies naturally impose some ordering constraints: a value cannot be used before it is computed.
|
||||||
|
Beyond these constraints, the evaluator is free to choose any order.
|
||||||
|
|
||||||
|
The order in which side effects such as [`builtins.trace`](@docroot@/language/builtins.md#builtins-trace) output occurs is not defined, but may be expected to follow data dependencies. <!-- we may want to be more specific about this. -->
|
||||||
|
|
||||||
|
In a lazy language, evaluation order is often opposite to expectations from strict languages.
|
||||||
|
For example, in `let wrap = x: { wrapped = x; }; in wrap (1 + 2)`, the function body produces a result (`{ wrapped = ...; }`) *before* evaluating `x`.
|
||||||
|
|
||||||
|
## Infinite recursion and stack overflow
|
||||||
|
|
||||||
|
During evaluation, two types of errors can occur when expressions reference themselves or call functions too deeply:
|
||||||
|
|
||||||
|
### Infinite recursion
|
||||||
|
|
||||||
|
This error occurs when a value depends on itself through a cycle, making it impossible to compute.
|
||||||
|
|
||||||
|
```nix
|
||||||
|
let x = x; in x
|
||||||
|
=> error: infinite recursion encountered
|
||||||
|
```
|
||||||
|
|
||||||
|
Infinite recursion happens at the value level when evaluating an expression requires evaluating the same expression again.
|
||||||
|
|
||||||
|
Despite the name, infinite recursion is cheap to compute and does not involve a stack overflow.
|
||||||
|
The cycle is finite and fairly easy to detect.
|
||||||
|
|
||||||
|
### Stack overflow
|
||||||
|
|
||||||
|
This error occurs when the call depth exceeds the maximum allowed limit.
|
||||||
|
|
||||||
|
```nix
|
||||||
|
let f = x: f (x + 1);
|
||||||
|
in f 0
|
||||||
|
=> error: stack overflow; max-call-depth exceeded
|
||||||
|
```
|
||||||
|
|
||||||
|
Stack overflow happens when too many function calls are nested without returning.
|
||||||
|
The maximum call depth is controlled by the [`max-call-depth` setting](@docroot@/command-ref/conf-file.md#conf-max-call-depth).
|
||||||
|
|
||||||
[C API]: @docroot@/c-api.md
|
[C API]: @docroot@/c-api.md
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@
|
||||||
| [Greater than or equal to][Comparison] | *expr* `>=` *expr* | none | 10 |
|
| [Greater than or equal to][Comparison] | *expr* `>=` *expr* | none | 10 |
|
||||||
| [Equality] | *expr* `==` *expr* | none | 11 |
|
| [Equality] | *expr* `==` *expr* | none | 11 |
|
||||||
| Inequality | *expr* `!=` *expr* | none | 11 |
|
| Inequality | *expr* `!=` *expr* | none | 11 |
|
||||||
| Logical conjunction (`AND`) | *bool* `&&` *bool* | left | 12 |
|
| [Logical conjunction] (`AND`) | *bool* `&&` *bool* | left | 12 |
|
||||||
| Logical disjunction (`OR`) | *bool* <code>\|\|</code> *bool* | left | 13 |
|
| [Logical disjunction] (`OR`) | *bool* <code>\|\|</code> *bool* | left | 13 |
|
||||||
| [Logical implication] | *bool* `->` *bool* | right | 14 |
|
| [Logical implication] | *bool* `->` *bool* | right | 14 |
|
||||||
| [Pipe operator] (experimental) | *expr* `\|>` *func* | left | 15 |
|
| [Pipe operator] (experimental) | *expr* `\|>` *func* | left | 15 |
|
||||||
| [Pipe operator] (experimental) | *func* `<\|` *expr* | right | 15 |
|
| [Pipe operator] (experimental) | *func* `<\|` *expr* | right | 15 |
|
||||||
|
|
@ -162,6 +162,9 @@ Update [attribute set] *attrset1* with names and values from *attrset2*.
|
||||||
The returned attribute set will have all of the attributes in *attrset1* and *attrset2*.
|
The returned attribute set will have all of the attributes in *attrset1* and *attrset2*.
|
||||||
If an attribute name is present in both, the attribute value from the latter is taken.
|
If an attribute name is present in both, the attribute value from the latter is taken.
|
||||||
|
|
||||||
|
This operator is [strict](@docroot@/language/evaluation.md#strictness) in both *attrset1* and *attrset2*.
|
||||||
|
That means that both arguments are evaluated to [weak head normal form](@docroot@/language/evaluation.md#values), so the attribute sets themselves are evaluated, but their attribute values are not evaluated.
|
||||||
|
|
||||||
[Update]: #update
|
[Update]: #update
|
||||||
|
|
||||||
## Comparison
|
## Comparison
|
||||||
|
|
@ -185,18 +188,88 @@ All comparison operators are implemented in terms of `<`, and the following equi
|
||||||
|
|
||||||
## Equality
|
## Equality
|
||||||
|
|
||||||
- [Attribute sets][attribute set] and [lists][list] are compared recursively, and therefore are fully evaluated.
|
- [Attribute sets][attribute set] are compared first by attribute names and then by items until a difference is found.
|
||||||
- Comparison of [functions][function] always returns `false`.
|
- [Lists][list] are compared first by length and then by items until a difference is found.
|
||||||
|
- Comparison of distinct [functions][function] returns `false`, but identical functions may be subject to [value identity optimization](#value-identity-optimization).
|
||||||
- Numbers are type-compatible, see [arithmetic] operators.
|
- Numbers are type-compatible, see [arithmetic] operators.
|
||||||
- Floating point numbers only differ up to a limited precision.
|
- Floating point numbers only differ up to a limited precision.
|
||||||
|
|
||||||
|
The `==` operator is [strict](@docroot@/language/evaluation.md#strictness) in both arguments; when comparing composite types ([attribute sets][attribute set] and [lists][list]), it is partially strict in their contained values: they are evaluated until a difference is found. <!-- this is woefully underspecified, affecting which expressions evaluate correctly; not just "ordering" or error messages. -->
|
||||||
|
|
||||||
|
### Value identity optimization
|
||||||
|
|
||||||
|
Nix performs equality comparisons of nested values by pointer equality or more abstractly, _identity_.
|
||||||
|
Nix semantics ideally do not assign a unique identity to values as they are created, but equality is an exception to this rule.
|
||||||
|
The disputable benefit of this is that it is more efficient, and it allows cyclical structures to be compared, e.g. `let x = { x = x; }; in x == x` evaluates to `true`.
|
||||||
|
However, as a consequence, it makes a function equal to itself when the comparison is made in a list or attribute set, in contradiction to a simple direct comparison.
|
||||||
|
|
||||||
[function]: ./syntax.md#functions
|
[function]: ./syntax.md#functions
|
||||||
|
|
||||||
[Equality]: #equality
|
[Equality]: #equality
|
||||||
|
|
||||||
|
## Logical conjunction
|
||||||
|
|
||||||
|
> **Syntax**
|
||||||
|
>
|
||||||
|
> *bool1* `&&` *bool2*
|
||||||
|
|
||||||
|
Logical AND. Equivalent to `if` *bool1* `then` *bool2* `else false`.
|
||||||
|
|
||||||
|
This operator is [strict](@docroot@/language/evaluation.md#strictness) in *bool1*, but only evaluates *bool2* if *bool1* is `true`.
|
||||||
|
|
||||||
|
> **Example**
|
||||||
|
>
|
||||||
|
> ```nix
|
||||||
|
> true && false
|
||||||
|
> => false
|
||||||
|
>
|
||||||
|
> false && throw "never evaluated"
|
||||||
|
> => false
|
||||||
|
> ```
|
||||||
|
|
||||||
|
[Logical conjunction]: #logical-conjunction
|
||||||
|
|
||||||
|
## Logical disjunction
|
||||||
|
|
||||||
|
> **Syntax**
|
||||||
|
>
|
||||||
|
> *bool1* `||` *bool2*
|
||||||
|
|
||||||
|
Logical OR. Equivalent to `if` *bool1* `then true` `else` *bool2*.
|
||||||
|
|
||||||
|
This operator is [strict](@docroot@/language/evaluation.md#strictness) in *bool1*, but only evaluates *bool2* if *bool1* is `false`.
|
||||||
|
|
||||||
|
> **Example**
|
||||||
|
>
|
||||||
|
> ```nix
|
||||||
|
> true || false
|
||||||
|
> => true
|
||||||
|
>
|
||||||
|
> true || throw "never evaluated"
|
||||||
|
> => true
|
||||||
|
> ```
|
||||||
|
|
||||||
|
[Logical disjunction]: #logical-disjunction
|
||||||
|
|
||||||
## Logical implication
|
## Logical implication
|
||||||
|
|
||||||
Equivalent to `!`*b1* `||` *b2* (or `if` *b1* `then` *b2* `else true`)
|
> **Syntax**
|
||||||
|
>
|
||||||
|
> *bool1* `->` *bool2*
|
||||||
|
|
||||||
|
Logical implication. Equivalent to `!`*bool1* `||` *bool2* (or `if` *bool1* `then` *bool2* `else true`).
|
||||||
|
|
||||||
|
This operator is [strict](@docroot@/language/evaluation.md#strictness) in *bool1*, but only evaluates *bool2* if *bool1* is `true`.
|
||||||
|
|
||||||
|
> **Example**
|
||||||
|
>
|
||||||
|
> ```nix
|
||||||
|
> true -> false
|
||||||
|
> => false
|
||||||
|
>
|
||||||
|
> false -> throw "never evaluated"
|
||||||
|
> => true
|
||||||
|
> ```
|
||||||
|
|
||||||
[Logical implication]: #logical-implication
|
[Logical implication]: #logical-implication
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue