diff --git a/doc/manual/source/language/evaluation.md b/doc/manual/source/language/evaluation.md index 980942c92..dff429776 100644 --- a/doc/manual/source/language/evaluation.md +++ b/doc/manual/source/language/evaluation.md @@ -74,4 +74,48 @@ in f { x = throw "error"; y = throw "error"; } => "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. + +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 diff --git a/doc/manual/source/language/operators.md b/doc/manual/source/language/operators.md index ab74e8a99..f2a7f0baa 100644 --- a/doc/manual/source/language/operators.md +++ b/doc/manual/source/language/operators.md @@ -23,8 +23,8 @@ | [Greater than or equal to][Comparison] | *expr* `>=` *expr* | none | 10 | | [Equality] | *expr* `==` *expr* | none | 11 | | Inequality | *expr* `!=` *expr* | none | 11 | -| Logical conjunction (`AND`) | *bool* `&&` *bool* | left | 12 | -| Logical disjunction (`OR`) | *bool* \|\| *bool* | left | 13 | +| [Logical conjunction] (`AND`) | *bool* `&&` *bool* | left | 12 | +| [Logical disjunction] (`OR`) | *bool* \|\| *bool* | left | 13 | | [Logical implication] | *bool* `->` *bool* | right | 14 | | [Pipe operator] (experimental) | *expr* `\|>` *func* | left | 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*. 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 ## Comparison @@ -185,18 +188,88 @@ All comparison operators are implemented in terms of `<`, and the following equi ## Equality -- [Attribute sets][attribute set] and [lists][list] are compared recursively, and therefore are fully evaluated. -- Comparison of [functions][function] always returns `false`. +- [Attribute sets][attribute set] are compared first by attribute names and then by items until a difference is found. +- [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. - 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. + +### 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 [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 -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