2022-03-03 16:10:47 +03:00
|
|
|
---
|
|
|
|
slug: syntax
|
|
|
|
---
|
|
|
|
|
2022-01-14 16:56:39 +03:00
|
|
|
# Nickel Syntax
|
|
|
|
|
2022-06-16 18:59:42 +03:00
|
|
|
## Identifiers
|
|
|
|
|
2022-08-02 16:41:43 +03:00
|
|
|
Nickel identifiers start with an alphabetic character, followed by zero or more
|
|
|
|
alphanumeric characters, `_` (underscores) or `'` (single quotes). For example,
|
|
|
|
`this-isn't-invalid` is a valid identifier.
|
2022-06-16 18:59:42 +03:00
|
|
|
|
2022-01-20 13:47:05 +03:00
|
|
|
## Simple values
|
2022-01-14 16:56:39 +03:00
|
|
|
|
2022-09-13 17:33:30 +03:00
|
|
|
There are four basic kinds of values in Nickel :
|
2022-08-02 16:41:43 +03:00
|
|
|
|
|
|
|
1. numeric values
|
|
|
|
2. boolean values
|
|
|
|
3. strings
|
|
|
|
4. enum tags
|
2022-03-03 16:10:47 +03:00
|
|
|
|
2022-01-14 16:56:39 +03:00
|
|
|
### Numeric values
|
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
Nickel has support for numbers, positive and negative, with or without
|
|
|
|
decimals. Internally, those numbers are stored as arbitrary precision rationals,
|
|
|
|
meaning that basic arithmetic operations (addition, subtraction, division and
|
|
|
|
multiplication) don't incur rounding errors. Numbers are deserialized as 64-bit
|
|
|
|
floating point numbers, in line with common JSON implementations.
|
|
|
|
|
|
|
|
Epxonentation is supported using the `std.number.pow` function. If the exponent
|
|
|
|
is exactly representable as an integer between `-2^63` and `2^64 - 1`, the
|
|
|
|
result is computed exactly. However, raising a number to a non-integral power
|
|
|
|
can incur rounding errors: both operands will be converted to the nearest 64-bit
|
|
|
|
floating point numbers, the power is computed as a 64-bit floating point number
|
|
|
|
as well, and then converted back to an arbitrary precision rational number.
|
|
|
|
|
|
|
|
Numbers are serialized as integers whenever possible, that is, when they
|
|
|
|
fit exactly into a 64-bit signed integer or a 64-bit unsigned integer. They
|
|
|
|
are serialized as a 64-bit float otherwise. The latter conversion might lose
|
|
|
|
precision as well, for example when serializing `1/3`.
|
|
|
|
|
2023-07-17 17:50:47 +03:00
|
|
|
Here are some examples of number literals in Nickel; scientific notation for
|
|
|
|
decimal is supported:
|
2022-01-14 16:56:39 +03:00
|
|
|
|
2022-03-07 14:27:09 +03:00
|
|
|
```nickel
|
2022-01-14 16:56:39 +03:00
|
|
|
1
|
|
|
|
0.543
|
2023-07-17 17:50:47 +03:00
|
|
|
1.7e217
|
|
|
|
-3e-3
|
2022-01-14 16:56:39 +03:00
|
|
|
-1000000
|
|
|
|
-6.8
|
|
|
|
```
|
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
There are some predefined operators for working with numbers:
|
2022-01-14 16:56:39 +03:00
|
|
|
| Operator | Description | Example |
|
|
|
|
|:--------:|:----------------------------------------------------:|:-------------:|
|
|
|
|
| + | The addition operator | `1 + 2 = 3` |
|
|
|
|
| \- | The subtraction operator | `1 - 2 = -1` |
|
|
|
|
| * | The multiplication operator | `1 * 2 = 2` |
|
|
|
|
| / | The division operator | `1 / 2 = 0.5` |
|
|
|
|
| % | The modulo operator (returns the *signed* remainder) | `5 % 3 = 2` |
|
|
|
|
|
2022-08-02 16:41:43 +03:00
|
|
|
> **Remark about the `-` operator:** Since `-` can be used inside an identifier,
|
|
|
|
> the subtraction operators **needs** to be surrounded by spaces: write `a - b`,
|
|
|
|
> not `a-b`. `1-2` works as expected, because `1` and `2` aren't identifiers.
|
2022-01-20 15:24:53 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
Numbers can be compared using the following operators:
|
2022-01-14 16:56:39 +03:00
|
|
|
| Operator | Description | Example |
|
|
|
|
|:--------:|:----------------:|:---------:|
|
|
|
|
| == | Equal | `5 == 5` |
|
|
|
|
| != | Not Equal | `5 != 4` |
|
|
|
|
| < | Smaller than | `2 < 3` |
|
|
|
|
| > | Greater than | `1 > -5` |
|
|
|
|
| >= | Greater or Equal | `1 >= 1` |
|
|
|
|
| <= | Smaller or Equal | `-1 <= 6` |
|
|
|
|
|
2022-01-25 14:45:20 +03:00
|
|
|
In the table below, you will find the operators sorted from highest to lowest precedence:
|
2022-01-20 15:24:53 +03:00
|
|
|
| Operators | Associativity | Remark |
|
|
|
|
|:--------------------:|:-------------:|-----------------------------------------------|
|
2022-01-25 14:45:20 +03:00
|
|
|
| `( ... )` | | parentheses always have the highest precedence |
|
2022-02-07 15:19:35 +03:00
|
|
|
| `-` | | unary negation (as in `-1`) |
|
2022-01-20 15:24:53 +03:00
|
|
|
| `*`, `/`, `%` | left-to-right | |
|
|
|
|
| `+`, `-` | left-to-right | binary addition and subtraction |
|
|
|
|
| `<`, `>`, `=<`, `>=` | left-to-right | |
|
|
|
|
| `==`, `!=` | left-to-right | |
|
2022-01-14 16:56:39 +03:00
|
|
|
|
|
|
|
### Boolean values
|
|
|
|
|
|
|
|
The boolean values in Nickel are denoted `true` and `false`.
|
|
|
|
|
2022-08-02 16:41:43 +03:00
|
|
|
Nickel features the classical boolean operators *AND* (&&), *OR* (||) and *NOT*
|
|
|
|
(!). The *AND* and *OR* operators are lazy in the evaluation of the second
|
|
|
|
argument: for example, in `exp1 && exp2`, `exp2` is only evaluated if `exp1`
|
|
|
|
evaluates to `false`.
|
2022-01-14 16:56:39 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
Here are some examples of boolean operators in Nickel:
|
2022-08-02 16:41:43 +03:00
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2022-01-14 16:56:39 +03:00
|
|
|
> true && false
|
|
|
|
false
|
|
|
|
|
|
|
|
> false || true
|
|
|
|
true
|
|
|
|
|
|
|
|
> ! true
|
|
|
|
false
|
|
|
|
```
|
|
|
|
|
|
|
|
### Strings
|
|
|
|
|
2022-08-02 16:41:43 +03:00
|
|
|
Nickel can work with sequences of characters, or strings. Strings are enclosed
|
2023-04-18 12:45:27 +03:00
|
|
|
by `" ... "` for a single line string or by `m%" ... "%` for a multiline string.
|
|
|
|
They can be concatenated with the operator `++`. Strings must be UTF-8 valid.
|
|
|
|
In fact, as far as at all practicable, Nickel treats strings as sequences of
|
|
|
|
Unicode extended grapheme clusters and refuses to break them apart.
|
2022-01-14 16:56:39 +03:00
|
|
|
|
2022-08-02 16:41:43 +03:00
|
|
|
The string interpolation syntax is
|
|
|
|
`"%{ < expression that evaluates to a string > }"`.
|
2022-01-24 12:31:05 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
Here are some examples of string handling in Nickel:
|
2022-08-02 16:41:43 +03:00
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2022-01-14 16:56:39 +03:00
|
|
|
> "Hello, World!"
|
|
|
|
"Hello, World!"
|
|
|
|
|
2022-02-24 11:34:55 +03:00
|
|
|
> m%"Well, if this isn't a multiline string?
|
2022-12-08 16:29:55 +03:00
|
|
|
Yes it is, indeed it is"%
|
2023-05-04 18:44:20 +03:00
|
|
|
"Well, if this isn't a multiline string?
|
2022-01-14 16:56:39 +03:00
|
|
|
Yes it is, indeed it is"
|
|
|
|
|
2022-01-27 16:36:52 +03:00
|
|
|
> "Hello" ++ "World"
|
|
|
|
"HelloWorld"
|
|
|
|
|
2022-02-24 11:34:55 +03:00
|
|
|
> let h = "Hello" in "%{h} World"
|
2022-01-27 16:36:52 +03:00
|
|
|
"Hello World"
|
|
|
|
|
2022-02-24 11:34:55 +03:00
|
|
|
> let n = 5 in "The number %{n}."
|
2022-01-27 16:36:52 +03:00
|
|
|
error: Type error
|
|
|
|
|
2023-04-13 18:25:00 +03:00
|
|
|
> let n = 5 in "The number %{std.string.from_number n}."
|
2022-01-27 16:36:52 +03:00
|
|
|
"The number 5."
|
|
|
|
```
|
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
Multiline strings are useful for writing indented lines. The first and last
|
|
|
|
lines are ignored if they are empty or contain only spaces. Indentation that is
|
|
|
|
present on all lines of the string is stripped. This way, multiline strings can
|
|
|
|
be indented for nicer code formatting without producing unwanted whitespace in
|
|
|
|
the output. For example:
|
2022-08-02 16:41:43 +03:00
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2022-02-24 11:34:55 +03:00
|
|
|
> m%"
|
2023-04-18 12:45:27 +03:00
|
|
|
This line has no indentation.
|
|
|
|
This line is indented.
|
|
|
|
This line is even more indented.
|
|
|
|
This line has no more indentation.
|
2022-12-08 16:29:55 +03:00
|
|
|
"%
|
2022-01-25 15:25:10 +03:00
|
|
|
"This line has no indentation.
|
|
|
|
This line is indented.
|
|
|
|
This line is even more indented.
|
2022-03-03 16:10:47 +03:00
|
|
|
This line has no more indentation."
|
2022-01-27 16:36:52 +03:00
|
|
|
```
|
2022-01-25 15:25:10 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
The only special sequence in a multiline string is the string interpolation:
|
2022-08-02 16:41:43 +03:00
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2022-12-08 16:29:55 +03:00
|
|
|
> m%"Multiline\nString?"%
|
2022-01-27 16:36:52 +03:00
|
|
|
"Multiline\nString?"
|
|
|
|
|
2022-12-08 16:29:55 +03:00
|
|
|
> m%"Multiline%{"\n"}String"%
|
2022-01-27 16:36:52 +03:00
|
|
|
"Multiline
|
|
|
|
String"
|
|
|
|
```
|
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
A multiline string can be introduced and closed with multiple `%` signs, as
|
|
|
|
long as the number of `%` signs in the start delimiter equals the number in the
|
|
|
|
closing delimiter. If you want to use string interpolation, you must use the
|
|
|
|
same amount of `%` signs as in the delimiters. This can be useful for escaping
|
|
|
|
`"%` or `%{` sequences in a string:
|
2022-08-02 16:41:43 +03:00
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2022-12-08 16:29:55 +03:00
|
|
|
> m%%"Hello World"%%
|
2022-01-24 12:31:05 +03:00
|
|
|
"Hello World"
|
|
|
|
|
2022-12-08 16:29:55 +03:00
|
|
|
> m%%%%%"Hello World"%%%%%
|
2022-01-27 16:36:52 +03:00
|
|
|
"Hello World"
|
2022-01-24 12:31:05 +03:00
|
|
|
|
2022-12-08 16:29:55 +03:00
|
|
|
> let w = "World" in m%%"Hello %{w}"%%
|
2022-02-24 11:34:55 +03:00
|
|
|
"Hello %{w}"
|
2022-01-27 16:36:52 +03:00
|
|
|
|
2022-12-08 16:29:55 +03:00
|
|
|
> let w = "World" in m%%"Hello %%{w}"%%
|
2022-01-27 16:36:52 +03:00
|
|
|
"Hello World"
|
|
|
|
```
|
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
Multiline string interpolation is "indentation-aware". This means that you can
|
|
|
|
interpolate a string with indentation and the result will be as expected:
|
2022-02-23 13:32:10 +03:00
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2022-02-24 11:34:55 +03:00
|
|
|
> let log = m%"
|
2023-04-18 12:45:27 +03:00
|
|
|
if log:
|
|
|
|
print("log:", s)
|
|
|
|
"% in m%"
|
|
|
|
def concat(str_array, log=false):
|
|
|
|
res = []
|
|
|
|
for s in str_array:
|
|
|
|
%{log}
|
|
|
|
res.append(s)
|
|
|
|
return res
|
|
|
|
"%
|
2022-02-23 13:32:10 +03:00
|
|
|
"def concat(str_array, log=false):
|
2022-01-27 16:36:52 +03:00
|
|
|
res = []
|
2022-02-23 13:32:10 +03:00
|
|
|
for s in str_array:
|
2022-01-27 16:36:52 +03:00
|
|
|
if log:
|
|
|
|
print("log:", s)
|
|
|
|
res.append(s)
|
|
|
|
return res"
|
2022-01-14 16:56:39 +03:00
|
|
|
```
|
|
|
|
|
2023-03-09 15:52:06 +03:00
|
|
|
#### Symbolic Strings
|
2023-03-09 13:19:34 +03:00
|
|
|
|
2023-03-09 15:52:06 +03:00
|
|
|
Some tools targeted by Nickel require manipulating string-like values that are
|
|
|
|
not yet known at the time of evaluation, such as Terraform's computed values.
|
2023-03-09 18:08:25 +03:00
|
|
|
Others, like Nix, perform additional dependency tracking (see [Nix string
|
2023-03-09 15:52:06 +03:00
|
|
|
context][nix-string-context]). In both cases, we have to build and combine
|
|
|
|
string-like values which are more complex than bare strings, but for which using
|
2023-04-18 12:45:27 +03:00
|
|
|
a string syntax would still feel natural.
|
2023-03-09 13:19:34 +03:00
|
|
|
|
2023-03-09 15:52:06 +03:00
|
|
|
That is precisely the use-case for symbolic strings:
|
2023-03-09 14:41:51 +03:00
|
|
|
|
2023-03-09 13:19:34 +03:00
|
|
|
```nickel
|
|
|
|
{
|
|
|
|
args = [
|
|
|
|
"-c",
|
|
|
|
nix-s%"
|
|
|
|
%{inputs.gcc}/bin/gcc %{inputs.hello} -o hello
|
|
|
|
%{inputs.coreutils}/bin/mkdir -p $out/bin
|
|
|
|
%{inputs.coreutils}/bin/cp hello $out/bin/hello
|
|
|
|
"%,
|
|
|
|
],
|
|
|
|
..
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2023-03-09 15:52:06 +03:00
|
|
|
This example is an excerpt of a Nix configuration written in Nickel, emulating
|
2023-04-18 12:45:27 +03:00
|
|
|
Nix string contexts. The region delimited by `nix-s%"` and `"%"` on lines 4 to
|
|
|
|
8 is a symbolic string. The values `inputs.gcc`, `inputs.hello`, etc. aren't
|
|
|
|
actually strings, but arbitrary records, because they carry additional context.
|
|
|
|
Yet, they can be interpolated as if they were strings.
|
2023-03-09 13:19:34 +03:00
|
|
|
|
|
|
|
The idea behind symbolic strings is to offer a string-like syntax, but without
|
|
|
|
evaluating the expression as a string. Instead, the expression is returned in a
|
2023-03-09 18:10:50 +03:00
|
|
|
symbolic form - in practice, an array of fragments, where each fragment is
|
2023-03-09 14:41:51 +03:00
|
|
|
either a string or an arbitrary value that has been interpolated - and Nickel
|
|
|
|
lets the specific library (Terraform-Nickel, Nix-Nickel, etc.) handle it.
|
2023-03-09 13:19:34 +03:00
|
|
|
|
2023-03-09 14:41:51 +03:00
|
|
|
The prefix of a symbolic string is any valid identifier that doesn't start with
|
|
|
|
`_`, and ends with the suffix `-s`. Prefixes don't have any meaning for Nickel:
|
2023-04-18 12:45:27 +03:00
|
|
|
they're just a tag used by libraries consuming symbolic strings to distinguish
|
2023-03-09 14:41:51 +03:00
|
|
|
between several types of symbolic strings. Prefixes are also a visual marker for
|
|
|
|
the programmer.
|
2023-03-09 13:19:34 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
Besides the custom prefix, symbolic strings otherwise follow the same syntactic
|
2023-03-09 15:52:06 +03:00
|
|
|
rules as multiline strings: the prefix is followed by an arbitrary number of `%`
|
2023-03-09 18:10:27 +03:00
|
|
|
followed by `"`, and must be closed by `"` followed by the same number of `%`.
|
2023-03-09 15:52:06 +03:00
|
|
|
|
2023-03-09 13:19:34 +03:00
|
|
|
The technical details don't matter too much in practice. As a user of a library
|
|
|
|
which uses symbolic strings, remember that:
|
|
|
|
|
|
|
|
- a special string with a prefix ending in `-s` is a symbolic string. The
|
|
|
|
prefix (or prefixes) is defined by the library.
|
|
|
|
- it's a special syntax without pre-existing meaning for Nickel. The
|
|
|
|
specific meaning of each kind of symbolic string, and what it's used for
|
|
|
|
exactly, is defined by the library. All in all, symbolic strings simply
|
|
|
|
provide libraries with a way to overload string syntax and interpolation for
|
|
|
|
extended usages.
|
2023-03-09 14:41:51 +03:00
|
|
|
- the main operation supported by symbolic strings is interpolation: `%{value}`.
|
|
|
|
What interpolation means, and which values can be interpolated in a given
|
|
|
|
symbolic string is again defined by each library. Other string functions don't
|
2023-04-13 18:25:00 +03:00
|
|
|
work on symbolic strings (e.g. `std.string.length`, `std.string.characters`,
|
|
|
|
and so on), because they might not have any valid meaning. Instead, libraries
|
|
|
|
should export their own string API, if they support additional operations on
|
|
|
|
their symbolic strings.
|
2023-03-09 13:19:34 +03:00
|
|
|
|
|
|
|
The following examples show how symbolic strings are desugared:
|
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2023-03-09 13:19:34 +03:00
|
|
|
> mytag-s%"I'm %{"symbolic"} with %{"fragments"}"%
|
|
|
|
{
|
2023-04-26 12:13:26 +03:00
|
|
|
tag = 'SymbolicString,
|
|
|
|
prefix = 'mytag
|
2023-03-09 13:19:34 +03:00
|
|
|
fragments = [ "I'm ", "symbolic", " with ", "fragments" ],
|
|
|
|
}
|
|
|
|
|
|
|
|
> let terraform_computed_field = {
|
2023-04-26 12:13:26 +03:00
|
|
|
tag = 'TfComputed,
|
2023-03-09 13:19:34 +03:00
|
|
|
resource = "foo",
|
|
|
|
field = "id",
|
|
|
|
}
|
|
|
|
> tf-s%"id: %{terraform_computed_field}, port: %{5}"%
|
|
|
|
{
|
2023-04-26 12:13:26 +03:00
|
|
|
tag = 'SymbolicString
|
|
|
|
prefix = 'tf,
|
|
|
|
fragments = [ "id: ", { resource = "foo", field = "id", tag = 'TfComputed }, ", port: ", 5 ],
|
2023-03-09 13:19:34 +03:00
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2022-03-08 13:22:31 +03:00
|
|
|
#### Enum tags
|
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
Enumeration tags are used to express a choice among finitely many alternatives.
|
2023-04-26 12:13:26 +03:00
|
|
|
They are formed by writing a single quote `'` followed by any valid identifier
|
2023-04-18 12:45:27 +03:00
|
|
|
or by a quoted string. For example, `std.serialize` takes an export format as a
|
2023-04-26 12:13:26 +03:00
|
|
|
first argument, which is an enum tag among `'Json`, `'Toml` or `'Yaml`:
|
2022-03-08 13:22:31 +03:00
|
|
|
|
|
|
|
```nickel
|
2023-04-26 12:13:26 +03:00
|
|
|
> std.serialize 'Json {foo = 1}
|
2023-04-18 12:45:27 +03:00
|
|
|
"{
|
|
|
|
\"foo\": 1
|
|
|
|
}"
|
|
|
|
|
2023-04-26 12:13:26 +03:00
|
|
|
> std.serialize 'Toml {foo = 1}
|
2023-04-18 12:45:27 +03:00
|
|
|
"foo = 1
|
|
|
|
"
|
2022-03-08 13:22:31 +03:00
|
|
|
```
|
|
|
|
|
2023-04-26 12:13:26 +03:00
|
|
|
An enum tag `'foo` is serialized as the string `"foo"`:
|
2022-03-08 13:22:31 +03:00
|
|
|
|
|
|
|
```nickel
|
2023-04-26 12:13:26 +03:00
|
|
|
> std.serialize 'Json {foo = 'bar}
|
2023-04-18 12:45:27 +03:00
|
|
|
"{
|
|
|
|
\"foo\": \"bar\"
|
|
|
|
}"
|
2022-03-08 13:22:31 +03:00
|
|
|
```
|
|
|
|
|
|
|
|
While it's technically possible to just use strings in place of enum tags, using
|
2023-04-18 12:45:27 +03:00
|
|
|
an enum tag encodes the intent that only a finite number of alternatives can be
|
2022-03-08 13:22:31 +03:00
|
|
|
used for the corresponding value.
|
|
|
|
|
2022-07-28 20:07:48 +03:00
|
|
|
Additionally, the typechecker is aware of enums and can for example statically
|
|
|
|
enforce that only valid tags are passed to a function within a typed block. See
|
|
|
|
[the manual section on typing](./typing.md) for more details.
|
2022-03-08 13:22:31 +03:00
|
|
|
|
2022-02-07 15:19:35 +03:00
|
|
|
## Equality
|
2022-01-17 16:04:22 +03:00
|
|
|
|
2022-08-02 16:41:43 +03:00
|
|
|
Operators `==` and `!=` are used to compare values. Two values of different
|
|
|
|
types are never equal: that is, `==` doesn't perform implicit conversions.
|
2022-01-17 16:04:22 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
Here are some examples of equality comparisons in Nickel:
|
2022-08-02 16:41:43 +03:00
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2022-01-17 16:04:22 +03:00
|
|
|
> 1 == 1
|
|
|
|
true
|
|
|
|
|
|
|
|
> 5 == 5.0
|
|
|
|
true
|
|
|
|
|
|
|
|
> "Hello" == "Hello"
|
|
|
|
true
|
|
|
|
|
|
|
|
> "Hello" != "World"
|
|
|
|
true
|
|
|
|
|
|
|
|
> 5 == "Hello"
|
|
|
|
false
|
|
|
|
|
|
|
|
> true == "true"
|
|
|
|
false
|
|
|
|
```
|
2022-01-14 16:56:39 +03:00
|
|
|
|
2022-01-20 13:47:05 +03:00
|
|
|
## Composite values
|
2022-01-14 16:56:39 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
### Arrays
|
2022-08-02 16:41:43 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
An array is a sequence of values. Arrays are delimited by `[` and `]`, and
|
2022-08-02 16:41:43 +03:00
|
|
|
elements are separated with `,`.
|
2022-01-17 16:08:31 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
The following are valid Nickel arrays, for example:
|
2022-08-02 16:41:43 +03:00
|
|
|
|
2022-03-07 14:27:09 +03:00
|
|
|
```nickel
|
2022-01-17 16:08:31 +03:00
|
|
|
[1, 2, 3]
|
|
|
|
["Hello", "World"]
|
|
|
|
[1, true, "true"]
|
|
|
|
[]
|
|
|
|
```
|
2022-01-14 16:56:39 +03:00
|
|
|
|
2022-02-23 13:32:10 +03:00
|
|
|
Arrays can be concatenated with the operator `@`:
|
2022-08-02 16:41:43 +03:00
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2022-01-17 18:45:35 +03:00
|
|
|
> [1] @ [2, 3]
|
|
|
|
[ 1, 2, 3 ]
|
|
|
|
```
|
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
### Records
|
2022-08-02 16:41:43 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
Records are key-value storage, or in Nickel terms, field-value storage. They
|
|
|
|
are delimited by `{` and `}`, and elements are separated with `,`. A field
|
|
|
|
definition is written as `field = value`. The fields are strings, but can be
|
|
|
|
written without quotes `"` if they are valid identifiers. Values can be of
|
2022-08-02 16:41:43 +03:00
|
|
|
any type. Elements inside a record are unordered. Two records can be *merged*
|
|
|
|
together using the operator `&`. The reader can find more information about
|
|
|
|
merging in the [section on merging](./merging.md).
|
2022-01-14 16:56:39 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
Here are some valid Nickel records:
|
2022-08-02 16:41:43 +03:00
|
|
|
|
2022-03-07 14:27:09 +03:00
|
|
|
```nickel
|
2022-01-17 18:45:35 +03:00
|
|
|
{}
|
|
|
|
{a = 3}
|
2022-02-07 15:35:52 +03:00
|
|
|
{my_id_n5 = "my id number 5", "my id n4" = "my id number 4" }
|
2022-01-17 18:45:35 +03:00
|
|
|
{"5" = 5, six = 6}
|
|
|
|
```
|
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
Record fields can be accessed using the `.` operator :
|
2022-08-02 16:41:43 +03:00
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2022-01-17 18:45:35 +03:00
|
|
|
> { a = 1, b = 5 }.a
|
|
|
|
1
|
|
|
|
|
|
|
|
> { a = 1 }.b
|
|
|
|
error: Missing field
|
|
|
|
|
|
|
|
> { "1" = "one" }."1"
|
|
|
|
"one"
|
|
|
|
```
|
2022-01-14 16:56:39 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
It is possible to write records of records via *piecewise syntax*, where we
|
2022-08-02 16:41:43 +03:00
|
|
|
separate fields by dots:
|
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2022-01-24 12:40:43 +03:00
|
|
|
> { a = { b = 1 } }
|
|
|
|
{ a = { b = 1 } }
|
2022-03-03 16:10:47 +03:00
|
|
|
|
2022-01-24 12:40:43 +03:00
|
|
|
> { a.b = 1 }
|
|
|
|
{ a = { b = 1 } }
|
|
|
|
|
|
|
|
> { a.b = 1, a.c = 2, b = 3}
|
|
|
|
{ a = { b = 1, c = 2 }, b = 3 }
|
|
|
|
```
|
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
When fields are enclosed in double quotes (`"`), you can use string
|
2022-08-02 16:41:43 +03:00
|
|
|
interpolation to create or access fields:
|
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2022-02-24 11:34:55 +03:00
|
|
|
> let k = "a" in { "%{k}" = 1 }
|
2022-01-27 16:41:13 +03:00
|
|
|
{ a = 1 }
|
|
|
|
|
2022-02-24 11:34:55 +03:00
|
|
|
> let k = "a" in { a = 1 }."%{k}"
|
2022-01-27 16:41:13 +03:00
|
|
|
1
|
|
|
|
```
|
|
|
|
|
2022-01-25 15:26:47 +03:00
|
|
|
## Constructs
|
2022-01-14 16:56:39 +03:00
|
|
|
|
|
|
|
### If-Then-Else
|
2022-08-02 16:41:43 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
This construct allows conditional branching in your code. You can use it like
|
2022-08-02 16:41:43 +03:00
|
|
|
`if <bool expr> then <expr> else <expr>`.
|
2022-01-18 12:09:04 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
Here are some valid conditional expressions in Nickel:
|
2022-08-02 16:41:43 +03:00
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2022-01-18 12:09:04 +03:00
|
|
|
> if true then "TRUE :)" else "false :("
|
|
|
|
"TRUE :)"
|
|
|
|
|
|
|
|
> if false then "Not this one" else "This one"
|
|
|
|
"This one"
|
|
|
|
|
|
|
|
> if "forty-two" == 42 then "equal?" else "unequal"
|
|
|
|
"unequal"
|
2022-01-25 15:28:18 +03:00
|
|
|
|
|
|
|
> ["1"] @ (if 42 == "42" then ["3"] else ["2"]) @ ["3"]
|
|
|
|
["1", "2", "3"]
|
2022-01-18 12:09:04 +03:00
|
|
|
```
|
2022-01-14 16:56:39 +03:00
|
|
|
|
|
|
|
### Let-In
|
2022-08-02 16:41:43 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
A `let` binds an expression to a variable. It is used like `let <rec?>
|
|
|
|
<ident> = <expr> in <expr>`. The `rec` keyword makes the binding recursive,
|
|
|
|
enabling the use of `<ident>` within the bound
|
|
|
|
expression `<expr>`.
|
|
|
|
Currently, only a single variable can be bound per let binding.
|
2022-01-18 12:09:04 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
Here are some examples of let bindings in Nickel:
|
2022-08-02 16:41:43 +03:00
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2022-01-18 12:09:04 +03:00
|
|
|
> let r = { a = "a", b = "b" } in r.a
|
|
|
|
"a"
|
|
|
|
|
|
|
|
> let inner = { inside = true } in let outer = { outside = inner.inside } in outer.outside
|
|
|
|
true
|
|
|
|
|
|
|
|
> let a = 1 in let b = 2 in a + b
|
|
|
|
3
|
2022-04-20 14:04:40 +03:00
|
|
|
|
|
|
|
> let rec f = fun n => if n == 0 then n else n + f (n - 1) in f 10
|
|
|
|
55
|
|
|
|
|
|
|
|
> let rec fib = fun n => if n <= 2 then 1 else fib (n - 1) + fib (n - 2) in fib 9
|
|
|
|
34
|
|
|
|
|
2022-08-02 16:41:43 +03:00
|
|
|
> let rec repeat = fun n x => if n <= 0 then [] else repeat (n - 1) x @ [x] in
|
2023-04-18 12:45:27 +03:00
|
|
|
repeat 3 "foo"
|
2022-04-20 14:04:40 +03:00
|
|
|
["foo", "foo", "foo"]
|
2022-01-18 12:09:04 +03:00
|
|
|
```
|
2022-01-14 16:56:39 +03:00
|
|
|
|
2022-01-18 12:09:04 +03:00
|
|
|
## Functions
|
2022-08-02 16:41:43 +03:00
|
|
|
|
|
|
|
A function is declared using the `fun` keyword, then arguments separated with
|
|
|
|
spaces, and finally an arrow `=>` to add the body of the function. To call a
|
2023-04-18 12:45:27 +03:00
|
|
|
function, just write the arguments after it separated with spaces. Functions in
|
|
|
|
Nickel are curried: a function taking multiple arguments is actually a function
|
|
|
|
that takes a single argument and returns a function taking the rest of the
|
|
|
|
arguments, and so on.
|
2022-01-18 12:09:04 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
Here are some examples of function definitions in Nickel:
|
2022-08-02 16:41:43 +03:00
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2022-01-18 12:09:04 +03:00
|
|
|
> (fun a b => a + b) 1 2
|
|
|
|
3
|
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
> let add = fun a b => a + b in add 1 2
|
2022-01-18 12:09:04 +03:00
|
|
|
3
|
|
|
|
|
|
|
|
> let add = fun a b => a + b in
|
|
|
|
let add1 = add 1 in
|
|
|
|
add1 2
|
|
|
|
3
|
|
|
|
```
|
|
|
|
|
2022-08-02 16:41:43 +03:00
|
|
|
All existing infix operators in Nickel can be turned into functions by putting
|
2023-04-18 12:45:27 +03:00
|
|
|
them inside parentheses, for example:
|
2022-08-02 16:41:43 +03:00
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2022-01-25 16:47:43 +03:00
|
|
|
> 1 + 2
|
|
|
|
3
|
|
|
|
|
|
|
|
> (+) 1 2
|
|
|
|
3
|
|
|
|
|
|
|
|
> let increment = fun n => (+) 1 n in
|
|
|
|
increment 41
|
|
|
|
42
|
|
|
|
|
|
|
|
> let increment = (+) 1 in
|
|
|
|
increment 41
|
|
|
|
42
|
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
> let flatten = std.array.fold_right (@) [] in
|
|
|
|
flatten [[1, 2], [3], [4, 5]]
|
2022-01-25 16:47:43 +03:00
|
|
|
[ 1, 2, 3, 4, 5 ]
|
|
|
|
```
|
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
Functions may be composed using the *pipe operator*. The pipe operator allows
|
2022-08-02 16:41:43 +03:00
|
|
|
for a function application `f x` to be written as `x |> f`. This operator is
|
2023-04-18 12:45:27 +03:00
|
|
|
left-associative, so `x |> f |> g` will be interpreted as `g (f x)`. For example:
|
2022-08-02 16:41:43 +03:00
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2023-04-13 18:25:00 +03:00
|
|
|
> "Hello World" |> std.string.split " "
|
2022-01-25 16:47:43 +03:00
|
|
|
["Hello", "World"]
|
|
|
|
|
2022-02-07 15:19:35 +03:00
|
|
|
> "Hello World"
|
2023-04-13 18:25:00 +03:00
|
|
|
|> std.string.split " "
|
|
|
|
|> std.array.first
|
2022-01-25 16:47:43 +03:00
|
|
|
"Hello"
|
|
|
|
|
2022-02-07 15:19:35 +03:00
|
|
|
> "Hello World"
|
2023-04-13 18:25:00 +03:00
|
|
|
|> std.string.split " "
|
|
|
|
|> std.array.first
|
|
|
|
|> std.string.uppercase
|
2022-01-25 16:47:43 +03:00
|
|
|
"HELLO"
|
|
|
|
```
|
|
|
|
|
2023-04-13 18:59:25 +03:00
|
|
|
## Annotations
|
2022-08-02 16:41:43 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
Contract and type annotations help enforce additional properties of an
|
|
|
|
expression. They can be attached to any Nickel expression. See [the correctness
|
2023-04-13 18:59:25 +03:00
|
|
|
section](./correctness.md) for more details.
|
|
|
|
|
|
|
|
### Type annotations
|
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
A type annotation is introduced using `<expr> : <type>` and serves to delimit
|
|
|
|
a statically typed block which will be checked by the typechecker before
|
|
|
|
evaluation. A type annotation can be directly attached to the variable of a let-
|
|
|
|
binding `let <var> : <type> = <expr> in <body>` or to a record field declaration
|
|
|
|
`{<field> : <type> = <value>}` as well.
|
2023-04-13 18:59:25 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
A type wildcard `_` indicates that part of a type is unknown to the user (or
|
|
|
|
is not worth spelling out). The typechecker will attempt to infer it. Adding a
|
|
|
|
wildcard type annotation `: _` to an existing expression is particularly useful
|
|
|
|
for debugging, as it's the simplest way to have the typechecker run on an
|
|
|
|
expression without having to come up with a type.
|
2022-01-24 12:50:49 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
Here are some examples of type annotations in Nickel:
|
2022-08-02 16:41:43 +03:00
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2023-04-13 18:59:25 +03:00
|
|
|
> 5 : Number
|
|
|
|
5
|
2022-01-24 12:50:49 +03:00
|
|
|
|
2023-04-13 18:59:25 +03:00
|
|
|
> "hello" : String
|
|
|
|
"hello"
|
2022-01-24 12:50:49 +03:00
|
|
|
|
2023-04-13 18:59:25 +03:00
|
|
|
> "Hello," ++ " world!" : String
|
|
|
|
"Hello, world!"
|
2022-01-24 12:50:49 +03:00
|
|
|
|
2023-04-13 18:59:25 +03:00
|
|
|
> 5 + "a" : _
|
|
|
|
error: incompatible types
|
|
|
|
[..]
|
|
|
|
|
|
|
|
> (1 + 1 : Number) + ((`foo |> match { `foo => 1, _ => 2 }) : Number)
|
|
|
|
3
|
|
|
|
|
|
|
|
> let x : Number = "a" in x
|
|
|
|
error: incompatible types
|
|
|
|
[..]
|
|
|
|
|
|
|
|
> let complex_argument : _ -> Number = fun {field1, field2, field3} => field1 in
|
2023-04-18 12:45:27 +03:00
|
|
|
complex_argument {field1 = 5, field2 = null, field3 = false}
|
2023-04-13 18:59:25 +03:00
|
|
|
5
|
2022-01-24 12:50:49 +03:00
|
|
|
```
|
2022-01-24 13:46:15 +03:00
|
|
|
|
2023-04-13 18:59:25 +03:00
|
|
|
### Contract annotations
|
2022-01-24 13:46:15 +03:00
|
|
|
|
2023-04-13 18:59:25 +03:00
|
|
|
A contract annotation is introduced by `<exp> | <contract>` and serves to apply
|
|
|
|
a runtime check to an expression (among other things).
|
|
|
|
|
|
|
|
As detailed in the next section, `<expr>`, `<type>` and `<contract>` are in fact
|
2023-04-18 12:45:27 +03:00
|
|
|
syntactically all the same and can be arbitrary Nickel expressions in practice.
|
2022-01-24 13:46:15 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
Here are some examples of contract annotations in Nickel:
|
2022-08-02 16:41:43 +03:00
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2023-03-07 12:52:26 +03:00
|
|
|
> 5 | Number
|
2022-01-24 13:46:15 +03:00
|
|
|
5
|
|
|
|
|
|
|
|
> 5 | Bool
|
2023-03-08 17:48:53 +03:00
|
|
|
error: contract broken by a value.
|
|
|
|
[..]
|
2022-01-24 13:46:15 +03:00
|
|
|
|
2023-04-13 18:25:00 +03:00
|
|
|
> let SmallNumber = std.contract.from_predicate (fun x => x < 5) in
|
2023-04-18 12:45:27 +03:00
|
|
|
1 | SmallNumber
|
2022-01-24 13:46:15 +03:00
|
|
|
1
|
|
|
|
|
2023-04-13 18:25:00 +03:00
|
|
|
> let SmallNumber = std.contract.from_predicate (fun x => x < 5) in
|
2023-04-18 12:45:27 +03:00
|
|
|
10 | SmallNumber
|
2023-03-08 17:48:53 +03:00
|
|
|
error: contract broken by a value.
|
|
|
|
[..]
|
2022-01-24 13:46:15 +03:00
|
|
|
|
2023-04-13 18:25:00 +03:00
|
|
|
> let SmallNumber = std.contract.from_predicate (fun x => x < 5) in
|
|
|
|
let NotTooSmallNumber = std.contract.from_predicate (fun x => x >= 2) in
|
2023-03-07 12:52:26 +03:00
|
|
|
3 | Number
|
2023-04-18 12:45:27 +03:00
|
|
|
| SmallNumber
|
|
|
|
| NotTooSmallNumber
|
2022-01-24 13:46:15 +03:00
|
|
|
3
|
|
|
|
```
|
|
|
|
|
2023-04-13 18:59:25 +03:00
|
|
|
### Types
|
|
|
|
|
|
|
|
The Nickel syntax mixes both terms and types in the same namespace. The
|
|
|
|
following program is perfectly legal: `let value = Number -> (fun value label =>
|
|
|
|
value) in ((fun x => x + 1) : value)`. See the
|
|
|
|
[RFC002](https://github.com/tweag/nickel/blob/d723a3721c6b0fe9c4b856e889bb7211d6136665/rfcs/002-merge-types-terms-syntax.md)
|
|
|
|
for a detailed account of this design.
|
|
|
|
|
|
|
|
The documentation still makes a distinction between *types* and other
|
|
|
|
expressions, the former being constructs which are handled specially by the
|
|
|
|
typechecker and are listed below. However, any expression can be considered a
|
2023-04-18 12:45:27 +03:00
|
|
|
type (in the generic case, it will be considered as an opaque type), and type
|
2023-04-13 18:59:25 +03:00
|
|
|
constructors can also appear inside an expression (where they are understood as
|
|
|
|
their associated contract, which is indeed an expression, most often a
|
|
|
|
function).
|
|
|
|
|
|
|
|
Thus, placeholders such as `<source>`, `<target>` or `<type>` can actually be
|
2023-04-18 12:45:27 +03:00
|
|
|
substituted with any valid Nickel expression (which includes the type
|
2023-04-13 18:59:25 +03:00
|
|
|
constructors we've just listed), and types can appear anywhere.
|
|
|
|
|
|
|
|
Nickel features the following builtin types and type constructors:
|
|
|
|
|
|
|
|
- Primitive types: `Number`, `String`, `Bool`, and `Dyn` (the dynamic type, wich
|
|
|
|
represents any value)
|
2023-04-18 12:45:27 +03:00
|
|
|
- Arrays: `Array <type>` is an array whose elements are of type `<type>`.
|
|
|
|
- Dictionaries: `{_ : <type>}` is a record whose fields are of type `<type>`.
|
2023-04-26 12:13:26 +03:00
|
|
|
- Enums: `[| 'tag1, .., 'tagn |]` is an enumeration comprised of the alternatives
|
|
|
|
`'tag1`, .., `'tagn`. Tags have the same syntax as identifiers and must
|
|
|
|
be prefixed with a single quote `'`. Like record fields, they can however be
|
2023-04-13 18:59:25 +03:00
|
|
|
enclosed in double quotes if they contain special characters:
|
2023-04-26 12:13:26 +03:00
|
|
|
`'"tag with space"`.
|
2023-04-18 12:45:27 +03:00
|
|
|
- Arrows: `<source> -> <target>` is a function taking an argument of type
|
2023-04-13 18:59:25 +03:00
|
|
|
`<source>` and returns values of type `<target>`.
|
2023-04-18 12:45:27 +03:00
|
|
|
- Foralls: `forall var1 .. varn. <type>` is a polymorphic type quantifying over type
|
2023-04-13 18:59:25 +03:00
|
|
|
variables `var1`, .., `varn`.
|
2023-04-18 12:45:27 +03:00
|
|
|
- Records: see the next section [Record types](#record-types).
|
2023-04-13 18:59:25 +03:00
|
|
|
|
|
|
|
Type variables bound by a `forall` are only visible inside types (any of the
|
2023-04-18 12:45:27 +03:00
|
|
|
constructor listed above). As soon as a term expression appears under a `forall`
|
2023-04-13 18:59:25 +03:00
|
|
|
binder, the type variables aren't in scope anymore:
|
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2023-04-13 18:59:25 +03:00
|
|
|
> forall a. a -> (a -> a) -> {_ : {foo : a}}
|
|
|
|
<func>
|
|
|
|
|
|
|
|
> forall a. a -> (a -> (fun x => a))
|
|
|
|
error: unbound identifier
|
|
|
|
┌─ repl-input-4:1:32
|
|
|
|
│
|
|
|
|
1 │ forall a. a -> (a -> (fun x => a))
|
|
|
|
│ ^ this identifier is unbound
|
|
|
|
```
|
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
Here are some examples of more complicated types in Nickel:
|
2022-08-02 16:41:43 +03:00
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2023-04-13 18:59:25 +03:00
|
|
|
> let f : forall a. a -> a = fun x => x in (f 5 : Number)
|
2022-01-24 13:46:15 +03:00
|
|
|
5
|
|
|
|
|
2023-04-13 18:59:25 +03:00
|
|
|
> {foo = [[1]]} : {foo : Array (Array Number)}
|
|
|
|
{ foo = [ [ 1 ] ] }
|
|
|
|
|
|
|
|
> let select
|
2023-04-26 12:13:26 +03:00
|
|
|
: forall a. {left: a, right: a} -> [| 'left, 'right |] -> a
|
2023-04-13 18:59:25 +03:00
|
|
|
= fun {left, right} =>
|
|
|
|
match {
|
2023-04-26 12:13:26 +03:00
|
|
|
'left => left,
|
|
|
|
'right => right,
|
2023-04-13 18:59:25 +03:00
|
|
|
}
|
|
|
|
in
|
2023-04-26 12:13:26 +03:00
|
|
|
(select {left = true, right = false} 'left) : Bool
|
2022-01-24 13:46:15 +03:00
|
|
|
true
|
2023-04-13 18:59:25 +03:00
|
|
|
|
|
|
|
> let add_foo : forall a. {_: a} -> a -> {_: a} = fun dict value =>
|
|
|
|
record.insert "foo" value dict
|
|
|
|
in
|
|
|
|
add_foo {bar = 1} 5 : _
|
|
|
|
{ bar = 1, foo = 5 }
|
|
|
|
|
|
|
|
> {foo = 1, bar = "string"} : {_ : Number}
|
|
|
|
error: incompatible types
|
|
|
|
┌─ repl-input-12:1:17
|
|
|
|
│
|
|
|
|
1 │ {foo = 1, bar = "string"} : {_ : Number}
|
|
|
|
│ ^^^^^^^^ this expression
|
|
|
|
│
|
|
|
|
= The type of the expression was expected to be `Number`
|
|
|
|
= The type of the expression was inferred to be `String`
|
|
|
|
= These types are not compatible
|
|
|
|
```
|
|
|
|
|
|
|
|
#### Record types
|
|
|
|
|
|
|
|
Record types are syntactically a restricted subset of record literals. They are
|
|
|
|
handled differently than normal record literals with respect to typechecking.
|
|
|
|
|
|
|
|
A record literal is a record type if:
|
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
- No field has a defined value: there are only fields without definition.
|
2023-04-13 18:59:25 +03:00
|
|
|
- Each field has exactly one type annotation
|
|
|
|
- Each field doesn't have any other metadata attached (see [Metadata](#metadata))
|
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
If these properties are satisfied, a record literal is considered to be a
|
2023-04-13 18:59:25 +03:00
|
|
|
record type by the typechecker.
|
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
A record literal which is interpreted as a record type may have a *record
|
|
|
|
tail*. A tail is written like `{ <fields> ; <tail> }`. It appears at the end of
|
|
|
|
the field declarations and is preceded by `;`. The tail `<tail>` itself must be
|
|
|
|
a valid identifier.
|
|
|
|
|
2023-04-13 18:59:25 +03:00
|
|
|
Trying to attach a tail `; tail` to a record literal which isn't a record type
|
|
|
|
is a parse error.
|
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
Here are some examples of record types in Nickel:
|
2023-04-13 18:59:25 +03:00
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2023-04-13 18:59:25 +03:00
|
|
|
> {foo = 1, bar = "foo" } : {foo : Number, bar: String}
|
|
|
|
{ bar = "foo", foo = 1 }
|
|
|
|
|
|
|
|
> {foo.bar = 1, baz = 2} : {foo: {bar : Number}, baz : Number}
|
|
|
|
{ baz = 2, foo = { bar = 1 } }
|
2023-04-18 12:45:27 +03:00
|
|
|
```
|
2023-04-13 18:59:25 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
Here, the right-hand side is missing a type annotation for `baz`, so it doesn't
|
|
|
|
qualify as a record type and is parsed as a record contract. This throws an
|
|
|
|
"incompatible types" error:
|
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2023-04-18 12:45:27 +03:00
|
|
|
> {foo = 1, bar = "foo" } : {foo : Number, bar : String, baz}
|
2023-04-13 18:59:25 +03:00
|
|
|
error: incompatible types
|
|
|
|
┌─ repl-input-6:1:1
|
|
|
|
│
|
|
|
|
1 │ {foo = 1, bar = "foo" } : {foo : Number, bar : String, baz}
|
|
|
|
│ ^^^^^^^^^^^^^^^^^^^^^^^ this expression
|
|
|
|
│
|
|
|
|
= The type of the expression was expected to be `{ bar : String, baz, foo : Numb…` (a contract)
|
|
|
|
= The type of the expression was inferred to be `{bar: _a, foo: _b}`
|
|
|
|
= Static types and contracts are not compatible
|
2023-04-18 12:45:27 +03:00
|
|
|
```
|
|
|
|
|
|
|
|
If there's a metadata annotation apart from the type, the record cannot be
|
|
|
|
parsed as a type. Consequently, it is considered a record contract and converted
|
|
|
|
into an opaque type, yielding an error:
|
2023-04-13 18:59:25 +03:00
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2023-04-18 12:45:27 +03:00
|
|
|
> {foo = 1, bar = "foo" } : {foo : Number, bar : String | optional}
|
2023-04-13 18:59:25 +03:00
|
|
|
error: incompatible types
|
|
|
|
[..]
|
2023-04-18 12:45:27 +03:00
|
|
|
```
|
2023-04-13 18:59:25 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
While in the following `MyDyn` isn't a proper type, the record literal `{foo :
|
|
|
|
Number, bar : MyDyn}` respects all the requirements for a record type and is
|
|
|
|
parsed as such:
|
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2023-04-18 12:45:27 +03:00
|
|
|
> let MyDyn = fun label value => value in
|
|
|
|
{foo = 1, bar | MyDyn = "foo"} : {foo : Number, bar : MyDyn}
|
2023-04-13 18:59:25 +03:00
|
|
|
{ bar = "foo", foo = 1 }
|
|
|
|
```
|
|
|
|
|
|
|
|
## Metadata
|
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
Metadata annotations are used to attach type and contract annotations,
|
|
|
|
documentation, a merge priority or other decorations to record fields (and
|
|
|
|
record fields only). Multiple metadata annotations can be chained. Metadata
|
|
|
|
is introduced with the syntax `<field_name> | <metadata1> | .. | <metadataN>
|
|
|
|
[= value]`.
|
2023-04-13 18:59:25 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
Documentation can be attached with `| doc <string>`. For example:
|
2023-04-13 18:59:25 +03:00
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2023-04-13 18:59:25 +03:00
|
|
|
> let record = {
|
2023-04-18 12:45:27 +03:00
|
|
|
value
|
|
|
|
| doc "The number five"
|
|
|
|
| default = 5
|
|
|
|
}
|
|
|
|
> :query record value
|
2023-04-13 18:59:25 +03:00
|
|
|
• default: 5
|
|
|
|
• documentation: The number five
|
|
|
|
|
|
|
|
> {
|
2023-04-18 12:45:27 +03:00
|
|
|
truth
|
|
|
|
| Bool
|
|
|
|
| doc m%"
|
|
|
|
If something is true,
|
|
|
|
it is based on facts rather than being invented or imagined,
|
|
|
|
and is accurate and reliable.
|
|
|
|
(Collins dictionary)
|
|
|
|
"%
|
|
|
|
= true,
|
|
|
|
}
|
2023-04-13 18:59:25 +03:00
|
|
|
{ truth = true }
|
2022-01-24 13:46:15 +03:00
|
|
|
```
|
|
|
|
|
2022-09-13 17:33:30 +03:00
|
|
|
Metadata can also set merge priorities using the following annotations:
|
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
- `default` is the lowest priority, usually used for default values that are
|
|
|
|
expected to be overridden somewhere
|
|
|
|
- `priority NN`, where `NN` is a number literal, is a numeral priority
|
|
|
|
- `force` is the highest priority
|
2022-09-13 17:33:30 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
If there is no priority specified, `priority 0` is the default. See more
|
|
|
|
about this in the [dedicated section on merging](./merging.md).
|
2022-08-02 16:41:43 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
Here are some examples using merge priorities in Nickel:
|
2022-08-02 16:41:43 +03:00
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2022-02-07 15:19:35 +03:00
|
|
|
> let Ais2ByDefault = { a | default = 2 } in
|
2023-04-18 12:45:27 +03:00
|
|
|
{} | Ais2ByDefault
|
2022-01-24 13:46:15 +03:00
|
|
|
{ a = 2 }
|
|
|
|
|
2022-02-07 15:19:35 +03:00
|
|
|
> let Ais2ByDefault = { a | default = 2 } in
|
2023-04-18 12:45:27 +03:00
|
|
|
{ a = 1 } | Ais2ByDefault
|
2022-01-24 13:46:15 +03:00
|
|
|
{ a = 1 }
|
2022-01-27 16:45:01 +03:00
|
|
|
|
|
|
|
> { foo | default = 1, bar = foo + 1 }
|
|
|
|
{ foo = 1, bar = 2 }
|
|
|
|
|
2022-02-07 15:19:35 +03:00
|
|
|
> {foo | default = 1, bar = foo + 1} & {foo = 2}
|
2022-01-27 16:45:01 +03:00
|
|
|
{ foo = 2, bar = 3 }
|
2022-09-13 17:33:30 +03:00
|
|
|
|
|
|
|
> {foo | force = 1, bar = foo + 1} & {foo = 2}
|
|
|
|
{ bar = 2, foo = 1 }
|
|
|
|
|
|
|
|
> {foo | priority 10 = 1} & {foo | priority 8 = 2} & {foo = 3}
|
|
|
|
{ foo = 1 }
|
|
|
|
|
|
|
|
> {foo | priority -1 = 1} & {foo = 2}
|
|
|
|
{ foo = 2 }
|
2022-01-24 13:46:15 +03:00
|
|
|
```
|
2023-03-08 17:48:53 +03:00
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
The `optional` annotation indicates that a field is not mandatory. It is usually
|
|
|
|
found in record contracts.
|
2023-03-08 17:48:53 +03:00
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2023-03-08 17:48:53 +03:00
|
|
|
> let Contract = {
|
|
|
|
foo | Num,
|
|
|
|
bar | Num
|
|
|
|
| optional,
|
|
|
|
}
|
|
|
|
> let value | Contract = {foo = 1}
|
|
|
|
> value
|
|
|
|
{ foo = 1 }
|
|
|
|
|
|
|
|
> {bar = 1} | Contract
|
|
|
|
error: missing definition for `foo`
|
|
|
|
[..]
|
|
|
|
```
|
|
|
|
|
2023-04-18 12:45:27 +03:00
|
|
|
The `not_exported` annotation indicates that a field should be skipped when a
|
|
|
|
record is serialized. This includes the output of the `nickel export` command:
|
2023-03-08 17:48:53 +03:00
|
|
|
|
2023-05-04 18:44:20 +03:00
|
|
|
```nickel
|
2023-03-08 17:48:53 +03:00
|
|
|
> let value = { foo = 1, bar | not_exported = 2}
|
|
|
|
> value
|
|
|
|
{ foo = 1, bar = 2 }
|
|
|
|
|
2023-04-26 12:13:26 +03:00
|
|
|
> std.serialize 'Json value
|
2023-03-08 17:48:53 +03:00
|
|
|
"{
|
|
|
|
"foo": 1
|
|
|
|
}"
|
|
|
|
```
|
2023-03-09 13:19:34 +03:00
|
|
|
|
|
|
|
[nix-string-context]: https://shealevy.com/blog/2018/08/05/understanding-nixs-string-context/
|