mirror of
https://github.com/tweag/nickel.git
synced 2024-09-11 11:47:03 +03:00
update comment syntax in anual, rfcs and READMEs
This commit is contained in:
parent
5fdc352391
commit
feb41abbf2
18
RATIONALE.md
18
RATIONALE.md
@ -2,12 +2,12 @@ Why Nickel ?
|
||||
============
|
||||
|
||||
There already exist quite a few languages with a similar purpose to Nickel:
|
||||
[CUE](https://cuelang.org/), [Dhall](https://dhall-lang.org/),
|
||||
[Jsonnet](https://jsonnet.org/),
|
||||
[Starlark](https://docs.bazel.build/versions/master/skylark/language.html), to
|
||||
[CUE](https:#cuelang.org/), [Dhall](https:#dhall-lang.org/),
|
||||
[Jsonnet](https:#jsonnet.org/),
|
||||
[Starlark](https:#docs.bazel.build/versions/master/skylark/language.html), to
|
||||
mention the closest contenders. So why Nickel ?
|
||||
|
||||
Nickel originated as an effort to detach the [Nix](https://nixos.org/)
|
||||
Nickel originated as an effort to detach the [Nix](https:#nixos.org/)
|
||||
expression language from the Nix package manager, while adding typing
|
||||
capabilities and improve modularity. We found that in practice, Nix is a simple
|
||||
yet expressive language which is particularly well fitted to build programmable
|
||||
@ -144,8 +144,8 @@ never requires recursion, this is not the case with library code. Allowing
|
||||
recursion makes it possible for programmers to implement new generic
|
||||
functionalities \[2\].
|
||||
|
||||
\[1\]: [Why Dhall is not Turing complete](http://neilmitchell.blogspot.com/2020/11/turing-incomplete-languages.html)\
|
||||
\[2\]: [Turing incomplete languages](http://www.haskellforall.com/2020/01/why-dhall-advertises-absence-of-turing.html)
|
||||
\[1\]: [Why Dhall is not Turing complete](http:#neilmitchell.blogspot.com/2020/11/turing-incomplete-languages.html)\
|
||||
\[2\]: [Turing incomplete languages](http:#www.haskellforall.com/2020/01/why-dhall-advertises-absence-of-turing.html)
|
||||
|
||||
### Side-Effects
|
||||
As for Turing-completeness, most of these languages also forbid side-effects.
|
||||
@ -160,7 +160,7 @@ variables.
|
||||
|
||||
However, sometimes the situation does not fit in a rigid framework: as for
|
||||
Turing-completeness, there may be cases which mandates side-effects. An example
|
||||
is when writing [Terraform](https://www.terraform.io/) configurations, some
|
||||
is when writing [Terraform](https:#www.terraform.io/) configurations, some
|
||||
external values (an IP) used somewhere in the configuration may only be known
|
||||
once another part of the configuration has been evaluated and executed
|
||||
(deploying machines, in this context). Reading this IP is a side-effect, even if
|
||||
@ -176,7 +176,7 @@ specific use-cases.
|
||||
Let's compare Nickel with the languages cited at the beginning: Starlark, Dhall, CUE and Jsonnet.
|
||||
|
||||
### Starlark: the standard package
|
||||
Starlark is a language originally designed for the [Bazel](https://bazel.build/)
|
||||
Starlark is a language originally designed for the [Bazel](https:#bazel.build/)
|
||||
build system, but it can also be used independently as a configuration language.
|
||||
It is a dialect of Python and includes the following classical features:
|
||||
|
||||
@ -198,7 +198,7 @@ code and prevents the expression of data schemas inside the language.
|
||||
|
||||
### Dhall: powerful type system
|
||||
Dhall is heavily inspired by Nix, to which it adds a [powerful type
|
||||
system](https://github.com/dhall-lang/dhall-lang/blob/master/standard/README.md#summary).
|
||||
system](https:#github.com/dhall-lang/dhall-lang/blob/master/standard/README.md#summary).
|
||||
Because of its complexity, the type system only supports a limited type
|
||||
inference. This can lead to code that is sometimes heavy on type annotations,
|
||||
as in the following example:
|
||||
|
50
README.md
50
README.md
@ -1,6 +1,6 @@
|
||||
# Nickel
|
||||
|
||||
[![Continuous integration](https://github.com/tweag/nickel/workflows/Continuous%20integration/badge.svg)](https://github.com/tweag/nickel/actions?query=branch%3Amaster)
|
||||
[![Continuous integration](https:#github.com/tweag/nickel/workflows/Continuous%20integration/badge.svg)](https:#github.com/tweag/nickel/actions?query=branch%3Amaster)
|
||||
|
||||
Nickel is the cheap configuration language.
|
||||
|
||||
@ -50,18 +50,18 @@ configuration, be it for a single app, a machine, whole infrastructure, or a
|
||||
build system.
|
||||
|
||||
The motivating use cases are in particular:
|
||||
- The [Nix package manager](https://nixos.org/): Nix is a declarative package
|
||||
- The [Nix package manager](https:#nixos.org/): Nix is a declarative package
|
||||
manager using its own language for specifying packages. Nickel is an
|
||||
evolution of the Nix language, while trying to overcome some of its
|
||||
limitations.
|
||||
- Infrastructure as code: infrastructure is becoming increasingly complex,
|
||||
requiring a rigorous approach to deployment, modification and configuration.
|
||||
This is where a declarative approach also shines, as adopted by
|
||||
[Terraform](https://www.terraform.io/),
|
||||
[NixOps](https://github.com/NixOS/nixops) or
|
||||
[Kubernetes](https://kubernetes.io/), all requiring potentially complex
|
||||
[Terraform](https:#www.terraform.io/),
|
||||
[NixOps](https:#github.com/NixOS/nixops) or
|
||||
[Kubernetes](https:#kubernetes.io/), all requiring potentially complex
|
||||
generation of configuration.
|
||||
- Build systems: build systems (like [Bazel](https://bazel.build/)) need
|
||||
- Build systems: build systems (like [Bazel](https:#bazel.build/)) need
|
||||
a specification of the dependency graph.
|
||||
|
||||
Most aforementioned projects have their own bespoke configuration language. See
|
||||
@ -74,10 +74,10 @@ abstractions or just feel ad hoc. Nickel buys you more for less.
|
||||
### Run
|
||||
|
||||
1. Start Nickel
|
||||
* with [flake-enabled](https://nixos.wiki/wiki/Flakes) Nix directly
|
||||
* with [flake-enabled](https:#nixos.wiki/wiki/Flakes) Nix directly
|
||||
with `nix run nickel` (which pulls it from the global flakes
|
||||
registry), or with `nix run github:tweag/nickel` (which pulls it
|
||||
from the repo). You can use [our binary cache](https://nickel.cachix.org) to
|
||||
from the repo). You can use [our binary cache](https:#nickel.cachix.org) to
|
||||
prevent rebuilding a lot of packages. You pass in arguments with
|
||||
an extra `--` as in `nix run nickel -- repl`,
|
||||
* with `./nickel`, after [building](#Build) this repo, depending on the
|
||||
@ -121,19 +121,19 @@ for help about a specific subcommand.
|
||||
|
||||
Nickel has syntax highlighting plugins for Vim/Neovim, and VSCode.
|
||||
In-editor diagnostics, type hints, and auto-completion are provided by the Nickel Language Server.
|
||||
Please follow [this guide](https://github.com/tweag/nickel/tree/master/lsp) to setup syntax highlighting and NLS.
|
||||
Please follow [this guide](https:#github.com/tweag/nickel/tree/master/lsp) to setup syntax highlighting and NLS.
|
||||
|
||||
### Build
|
||||
|
||||
[rust-guide]: https://doc.rust-lang.org/cargo/getting-started/installation.html
|
||||
[rust-guide]: https:#doc.rust-lang.org/cargo/getting-started/installation.html
|
||||
|
||||
1. Download build dependencies:
|
||||
- **With Nix**: If you have [Nix](https://nixos.org/nix) installed:
|
||||
- **With Nix**: If you have [Nix](https:#nixos.org/nix) installed:
|
||||
```console
|
||||
$ nix-shell shell.nix
|
||||
```
|
||||
to be dropped in a shell, ready to build. You can use [our binary
|
||||
cache](https://nickel.cachix.org) to prevent rebuilding a lot of
|
||||
cache](https:#nickel.cachix.org) to prevent rebuilding a lot of
|
||||
packages.
|
||||
- **Without Nix**: otherwise, follow [this guide][rust-guide] to install Rust
|
||||
and Cargo first.
|
||||
@ -175,43 +175,43 @@ and other important practical aspects are still being debated. We aim to
|
||||
transition from an experimental stage to a minimum viable product stage. The
|
||||
next points to deal with are:
|
||||
|
||||
- [Stdlib stabilization](https://github.com/tweag/nickel/issues/321)
|
||||
- [Overriding](https://github.com/tweag/nickel/pull/330)
|
||||
- [Stdlib stabilization](https:#github.com/tweag/nickel/issues/321)
|
||||
- [Overriding](https:#github.com/tweag/nickel/pull/330)
|
||||
- Memory management (use reference counting) & basic performance improvements
|
||||
- [List comprehensions](https://github.com/tweag/nickel/issues/80)
|
||||
- [Destructuring](https://github.com/tweag/nickel/issues/81)
|
||||
- [List comprehensions](https:#github.com/tweag/nickel/issues/80)
|
||||
- [Destructuring](https:#github.com/tweag/nickel/issues/81)
|
||||
|
||||
## Related projects and inspirations
|
||||
|
||||
- [Cue](https://cuelang.org/) is a configuration language with a focus on data
|
||||
- [Cue](https:#cuelang.org/) is a configuration language with a focus on data
|
||||
validation. It introduces a new constraint system backed by a solid theory
|
||||
which ensures strong guarantees about your code. It allows for very elegant
|
||||
schema specifications. In return, the cost to pay is to abandon functions
|
||||
and
|
||||
[Turing-completeness](https://en.wikipedia.org/wiki/Turing_completeness).
|
||||
[Turing-completeness](https:#en.wikipedia.org/wiki/Turing_completeness).
|
||||
Nickel's merge system is inspired by the one of CUE, even if since Nickel
|
||||
does have general functions and is Turing-complete, they are necessarily
|
||||
different.
|
||||
- [Nix](https://nixos.org/): The Nix language, or *Nix expressions*, is one of
|
||||
- [Nix](https:#nixos.org/): The Nix language, or *Nix expressions*, is one of
|
||||
the main inspirations for Nickel. It is a very simple yet powerful lazy
|
||||
functional language. We strive to retain this simplicity, while adding
|
||||
typing capabilities, modularity, and detaching the language from the Nix
|
||||
package manager.
|
||||
- [Dhall](https://dhall-lang.org/) is a statically typed configuration language.
|
||||
- [Dhall](https:#dhall-lang.org/) is a statically typed configuration language.
|
||||
It is also inspired by Nix, to which it adds a powerful static type system.
|
||||
However, this forces the programmer to annotate all of their code with types.
|
||||
- [Jsonnet](https://jsonnet.org/) is another language which could be dubbed as
|
||||
- [Jsonnet](https:#jsonnet.org/) is another language which could be dubbed as
|
||||
"JSON with functions" (and others things as well). It is a lazy functional
|
||||
language with object oriented features, among which inheritance is similar
|
||||
to Nickel's merge system. One big difference with Nickel is the absence of
|
||||
typing.
|
||||
- [Pulumi](https://www.pulumi.com/) is not a language in itself, but a cloud
|
||||
- [Pulumi](https:#www.pulumi.com/) is not a language in itself, but a cloud
|
||||
tool (like Terraform) where you can use your preferred language for
|
||||
describing your infrastructure. This is a different approach to the problem,
|
||||
with different trade-offs.
|
||||
- [Starlark](https://docs.bazel.build/versions/master/skylark/language.html) is
|
||||
the language of [Bazel](https://bazel.build/), which is a dialect of
|
||||
[Python](https://www.python.org/). It does not have types and recursion is
|
||||
- [Starlark](https:#docs.bazel.build/versions/master/skylark/language.html) is
|
||||
the language of [Bazel](https:#bazel.build/), which is a dialect of
|
||||
[Python](https:#www.python.org/). It does not have types and recursion is
|
||||
forbidden, making it not Turing-complete.
|
||||
|
||||
See [RATIONALE.md](./RATIONALE.md) for the design rationale and a more detailed
|
||||
|
@ -15,10 +15,10 @@ let x = (1 + 1 | Num) in x
|
||||
Contract can also be attached to identifiers in a definition:
|
||||
|
||||
```nickel
|
||||
// let-binding: equivalent to the previous example
|
||||
# let-binding: equivalent to the previous example
|
||||
let x | Num = 1 + 1 in x
|
||||
|
||||
// on a record field
|
||||
# on a record field
|
||||
{x | Num = 1 + 1}
|
||||
```
|
||||
|
||||
@ -165,7 +165,7 @@ let Between = fun min max =>
|
||||
contract.from_predicate (fun value =>
|
||||
value >= min &&
|
||||
value <= max) in
|
||||
// alternative without from_predicate
|
||||
# alternative without from_predicate
|
||||
let BetweenAlt = fun min max label value =>
|
||||
if builtin.is_num value &&
|
||||
value >= min &&
|
||||
@ -197,9 +197,9 @@ let Nullable = fun contract label value =>
|
||||
value
|
||||
else
|
||||
contract.apply contract label value in
|
||||
// succeeds
|
||||
# succeeds
|
||||
null | Nullable Num
|
||||
// succeeds too
|
||||
# succeeds too
|
||||
1 | Nullable Num
|
||||
```
|
||||
|
||||
@ -580,7 +580,7 @@ let NumBoolDict = fun label value =>
|
||||
|> record.fields
|
||||
|> array.foldl (fun acc field_name =>
|
||||
if string.is_match "^\\d+$" field_name then
|
||||
acc // unused and always null through iteration
|
||||
acc # unused and always null through iteration
|
||||
else
|
||||
contract.blame_with "field name `#{field_name}` is not a number" label
|
||||
) null in
|
||||
@ -628,7 +628,7 @@ Let us see if we indeed preserved laziness:
|
||||
|
||||
```
|
||||
nickel>let config | NumBoolDict = {
|
||||
"1" = 1 + "a", // Same as our previous "fail"
|
||||
"1" = 1 + "a", # Same as our previous "fail"
|
||||
"0" | doc "Some information" = true,
|
||||
}
|
||||
nickel>:query config."0"
|
||||
|
@ -37,7 +37,7 @@ Alternatively, you can repeat your types both at the function level and at the
|
||||
record level. It makes code more navigable and `query`-friendly, but at the
|
||||
expense of repetition and duplicated contract checks. It is also currently
|
||||
required for polymorphic functions because of [the following
|
||||
bug](https://github.com/tweag/nickel/issues/360). A better solution will
|
||||
bug](https:#github.com/tweag/nickel/issues/360). A better solution will
|
||||
probably be implemented in the future: type holes (TODO: MAY ACTUALLY BE AVAILABLE FOR RELEASE)
|
||||
|
||||
(NOT YET POSSIBLE)
|
||||
|
@ -40,7 +40,7 @@ machinery. On the other hand, checking this property at runtime on the final
|
||||
result is trivial.
|
||||
|
||||
Nevertheless, if you have ever faced puzzling [dynamic type
|
||||
errors](https://www.haskellforall.com/2021/01/dynamic-type-errors-lack-relevance.html),
|
||||
errors](https:#www.haskellforall.com/2021/01/dynamic-type-errors-lack-relevance.html),
|
||||
you may feel the need for something better. Bare dynamic typing is prone to
|
||||
irrelevant error messages, pointing to a location far from the problematic code
|
||||
in the source. This is especially true when working with functions, which may be
|
||||
@ -118,12 +118,12 @@ Here is the definition for `split`, but with a twist. We mistakenly forgot to
|
||||
wrap `pair.key` as an array before concatenating at line 6:
|
||||
|
||||
```nickel
|
||||
// lib.ncl
|
||||
# lib.ncl
|
||||
{
|
||||
split = fun pairs =>
|
||||
array.fold (fun pair acc =>
|
||||
{
|
||||
// problem: the right expression to use is [pair.key]
|
||||
# problem: the right expression to use is [pair.key]
|
||||
keys = acc.keys @ pair.key,
|
||||
values = acc.values @ [pair.value],
|
||||
})
|
||||
@ -135,7 +135,7 @@ wrap `pair.key` as an array before concatenating at line 6:
|
||||
And we call to split from our configuration:
|
||||
|
||||
```nickel
|
||||
// config.ncl
|
||||
# config.ncl
|
||||
let {split} = import "lib.ncl" in
|
||||
split [{key = "foo", value = 1}, {key = "bar", value = 2}]
|
||||
```
|
||||
|
@ -79,7 +79,7 @@ let Schema = {
|
||||
## Computation (compound expressions)
|
||||
|
||||
Some expressions are neither immediate data nor functions. Take for example the
|
||||
function application `array.map (fun s => "http://#{s}/index") servers`.
|
||||
function application `array.map (fun s => "http:##{s}/index") servers`.
|
||||
Usually, you should do **nothing**.
|
||||
|
||||
- *Inside configuration: nothing*. The function or operator you are using should
|
||||
|
@ -99,15 +99,15 @@ block*.
|
||||
|
||||
Example:
|
||||
```
|
||||
// Let binding
|
||||
# Let binding
|
||||
let f : Num -> Bool = fun x => x % 2 == 0 in
|
||||
|
||||
// Record field
|
||||
# Record field
|
||||
let r = {
|
||||
count : Num = 2354.45 * 4 + 100,
|
||||
} in
|
||||
|
||||
// Inline
|
||||
# Inline
|
||||
1 + ((if f 10 then 1 else 0) : Num)
|
||||
```
|
||||
|
||||
@ -300,7 +300,7 @@ write a type anntation. Here, `filter` is inferred to be of type `(Num -> Bool)
|
||||
**Note**:
|
||||
if you are a more type-inclined reader, you may wonder why the typechecker is
|
||||
not capable of inferring a polymorphic type for `filter` by itself. Indeed,
|
||||
[Hindley-Milner](https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system)
|
||||
[Hindley-Milner](https:#en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system)
|
||||
type-inference can precisely infer heading `foralls`, such that the previous
|
||||
rejected example would be accepted. We chose to abandon this so-called automatic
|
||||
generalization, because doing so just makes things simpler with respect to the
|
||||
@ -389,7 +389,7 @@ summed, without modifying the rest:
|
||||
```nickel
|
||||
let sum : forall r. {a : Num, b : Num | r} -> {a : Num, b : Num, sum : Num | r}
|
||||
= fun x => x $[ "sum" = x.a + x.b]
|
||||
in sum {a = 1, b = 2, c = 3} // {a=1, b=2, sum=3, c=3}
|
||||
in sum {a = 1, b = 2, c = 3} # {a=1, b=2, sum=3, c=3}
|
||||
```
|
||||
|
||||
Note that row polymorphism also works with enums, with the same intuition of a
|
||||
|
@ -20,7 +20,7 @@ This example defines a couple contracts:
|
||||
appears as such in the output. This contract is illustrative and by no mean
|
||||
exhaustive.
|
||||
- `Path`: define a valid `/`-separated path as a string. In particular, the
|
||||
`//` sequence is forbidden.
|
||||
`#` sequence is forbidden.
|
||||
- `SharedObjectFile`: define a file whose extension is `.so`.
|
||||
- `OptLevel`: optimization level, either `0`, `1` or `2`.
|
||||
- `Contract`: the schema of the end configuration.
|
||||
|
@ -1,8 +1,8 @@
|
||||
# Nickel Language Server
|
||||
|
||||
The Nickel Language Server (NLS) is a [language
|
||||
server](https://en.wikipedia.org/wiki/Language_Server_Protocol) for the
|
||||
[Nickel](https://www.nickel-lang.org/) programming language. NLS offers error
|
||||
server](https:#en.wikipedia.org/wiki/Language_Server_Protocol) for the
|
||||
[Nickel](https:#www.nickel-lang.org/) programming language. NLS offers error
|
||||
messages, type hints, and auto-completion right in your favorite LSP-enabled
|
||||
editor.
|
||||
|
||||
@ -18,7 +18,7 @@ use the Rust toolchain and don't want to install Nix.
|
||||
|
||||
### Using Nix (flakes)
|
||||
|
||||
The easiest way to install `nls` is using [Nix](https://nixos.org/).
|
||||
The easiest way to install `nls` is using [Nix](https:#nixos.org/).
|
||||
|
||||
**Important**: the following of this section assumes that you have a flake-enabled
|
||||
Nix (>= 2.4) and the experimental features `flakes` and `nix-command` enabled.
|
||||
@ -51,19 +51,19 @@ Alternatively, you can insall `nickel` and `nls` globally on older Nix versions
|
||||
without flakes via `nix-env`:
|
||||
|
||||
```
|
||||
git clone https://github.com/tweag/nickel.git
|
||||
git clone https:#github.com/tweag/nickel.git
|
||||
cd nickel
|
||||
nix-env -f . -i
|
||||
```
|
||||
|
||||
### Using Cargo
|
||||
|
||||
If you already have a working [`cargo`](https://doc.rust-lang.org/cargo/) installation, you can make `nls` available
|
||||
If you already have a working [`cargo`](https:#doc.rust-lang.org/cargo/) installation, you can make `nls` available
|
||||
globally (it will be built and stored inside the repository) without
|
||||
Nix:
|
||||
|
||||
```
|
||||
git clone https://github.com/tweag/nickel.git
|
||||
git clone https:#github.com/tweag/nickel.git
|
||||
cd nickel/lsp/nls
|
||||
cargo install --path .
|
||||
```
|
||||
@ -81,7 +81,7 @@ NLS is currently not available through the vscode marketplace, but this
|
||||
repository includes an extension that can be built locally via Nix (cf []()
|
||||
about the Nix setup).
|
||||
|
||||
- One-liner (using the [`jq`](https://stedolan.github.io/jq/) command):
|
||||
- One-liner (using the [`jq`](https:#stedolan.github.io/jq/) command):
|
||||
```
|
||||
code --install-extension $(nix build ./\#vscodeExtension --no-link --json | jq ".[0].outputs.vsix")
|
||||
```
|
||||
@ -103,7 +103,7 @@ The VS Code extension offers three configuration options:
|
||||
|
||||
### (Neo)Vim
|
||||
|
||||
Before proceeding install the [Nickel syntax highlighting plugin](https://github.com/nickel-lang/vim-nickel) using your Vim plugin manager.
|
||||
Before proceeding install the [Nickel syntax highlighting plugin](https:#github.com/nickel-lang/vim-nickel) using your Vim plugin manager.
|
||||
Without this plugin your LSP client may not start NLS on nickel source files.
|
||||
|
||||
With Vim-Plug:
|
||||
@ -114,7 +114,7 @@ Plug 'nickel-lang/vim-nickel'
|
||||
#### Neovim builtin LSP
|
||||
|
||||
`nls` is supported in
|
||||
[nvim-lspconfig](https://github.com/neovim/nvim-lspconfig). Using
|
||||
[nvim-lspconfig](https:#github.com/neovim/nvim-lspconfig). Using
|
||||
`nvim-lspconfig` setup `nls` like all your other LSP servers as described by the
|
||||
`nvim-lspconfig` ReadMe.
|
||||
|
||||
@ -129,12 +129,12 @@ Add an `nickel_ls` entry to your configuration. Type `:CocConfig` in Neovim (or
|
||||
```
|
||||
{
|
||||
"languageserver": {
|
||||
// Your other language servers configuration
|
||||
// ...,
|
||||
# Your other language servers configuration
|
||||
# ...,
|
||||
"nickel_ls": {
|
||||
"command": "nls",
|
||||
// You can enable performance tracing with:
|
||||
// "command": "nls --trace <file>",
|
||||
# You can enable performance tracing with:
|
||||
# "command": "nls --trace <file>",
|
||||
"rootPatterns": [
|
||||
".git"
|
||||
],
|
||||
|
@ -1,13 +1,13 @@
|
||||
### Using Makam
|
||||
|
||||
Makam is a dialect of lambda Prolog created and maintained by Antonis Stampoulis, more information [here](https://github.com/astampoulis/makam).
|
||||
Makam is a dialect of lambda Prolog created and maintained by Antonis Stampoulis, more information [here](https:#github.com/astampoulis/makam).
|
||||
This is an attempt to use it to define the semantics of the **Nickel** language.
|
||||
|
||||
He distributes it through [NPM](https://www.npmjs.com/package/makam), and on the [repo](https://github.com/astampoulis/makam) there's information on how to get it working.
|
||||
He distributes it through [NPM](https:#www.npmjs.com/package/makam), and on the [repo](https:#github.com/astampoulis/makam) there's information on how to get it working.
|
||||
|
||||
#### Using it on Nix
|
||||
|
||||
We use the `node2nix` helper, there's a [PR](https://github.com/NixOS/nixpkgs/pull/67703) to add it to nixpkgs, but for now this simple config should help.
|
||||
We use the `node2nix` helper, there's a [PR](https:#github.com/NixOS/nixpkgs/pull/67703) to add it to nixpkgs, but for now this simple config should help.
|
||||
|
||||
Just `nix-build -A makam` and then `result/bin/makam ./src/init.makam -`. Or run `result/bin/makam src/init.makam src/examples.makam` to run the examples.
|
||||
|
||||
|
@ -10,10 +10,10 @@ This document is a proposal for an overriding mechanism in Nickel. It is
|
||||
expected to evolve while we put these ideas in practice, but shall serve as
|
||||
a design and implementation baseline.
|
||||
|
||||
Related issues: [#103](https://github.com/tweag/nickel/issues/103),
|
||||
[#240](https://github.com/tweag/nickel/issues/240),
|
||||
[#255](https://github.com/tweag/nickel/issues/255),
|
||||
[#279](https://github.com/tweag/nickel/issues/279).
|
||||
Related issues: [#103](https:#github.com/tweag/nickel/issues/103),
|
||||
[#240](https:#github.com/tweag/nickel/issues/240),
|
||||
[#255](https:#github.com/tweag/nickel/issues/255),
|
||||
[#279](https:#github.com/tweag/nickel/issues/279).
|
||||
|
||||
## Context
|
||||
|
||||
@ -47,11 +47,11 @@ and returns a new updated record. It has the same semantics as our first
|
||||
snippet, but doesn't require to rewrite all unchanged fields.
|
||||
|
||||
It can have a builtin syntax, such as OCaml's `with`: `{record with field =
|
||||
new_value}`, Haskell's `record {field = newValue}`, Nix `//` operator `record //
|
||||
new_value}`, Haskell's `record {field = newValue}`, Nix `#` operator `record #
|
||||
{field = newValue}`, or Rust's syntax `RecordDataType {field: new_value,
|
||||
..record}`. There are more advanced programming techniques that make updating
|
||||
deeply nested records ergonomic such as
|
||||
[Lenses](https://www.fpcomplete.com/haskell/tutorial/lens/) in Haskell, but
|
||||
[Lenses](https:#www.fpcomplete.com/haskell/tutorial/lens/) in Haskell, but
|
||||
these rely too heavily on advanced language and typing features to be practical
|
||||
in Nickel.
|
||||
|
||||
@ -73,7 +73,7 @@ As explained in the next section though, this is not satisfying.
|
||||
|
||||
Nickel's records are different from the ones of OCaml, Haskell or Rust. They
|
||||
are lazy and recursive by default. They are thus better understood as
|
||||
[codata](https://link.springer.com/chapter/10.1007/978-3-030-17184-1_5)
|
||||
[codata](https:#link.springer.com/chapter/10.1007/978-3-030-17184-1_5)
|
||||
rather than data. Take the following example:
|
||||
|
||||
```nickel
|
||||
@ -136,7 +136,7 @@ having it implemented in user code leads to some general well-known issues:
|
||||
error reporting much harder.
|
||||
- It is potentially harder to make user land implementations efficient.
|
||||
|
||||
See [this gist](https://gist.github.com/edolstra/29ce9d8ea399b703a7023073b0dbc00d)
|
||||
See [this gist](https:#gist.github.com/edolstra/29ce9d8ea399b703a7023073b0dbc00d)
|
||||
for more details. We continue with an overview of existing mechanisms in Nix and
|
||||
related languages.
|
||||
|
||||
@ -158,7 +158,7 @@ rRepr = self: {
|
||||
```
|
||||
|
||||
`self` is a self-reference, akin to `this` in object oriented languages. It is
|
||||
computed as a [fixpoint](https://en.wikipedia.org/wiki/Fixed-point_combinator),
|
||||
computed as a [fixpoint](https:#en.wikipedia.org/wiki/Fixed-point_combinator),
|
||||
simply realized by auto-application in Nix, thanks to laziness:
|
||||
|
||||
```nix
|
||||
@ -173,15 +173,15 @@ the original representation:
|
||||
```nix
|
||||
let extension = {a = 2;}; in
|
||||
# The fixpoint of result is { a = 2; b = 3; }
|
||||
resultRepr = self: (rRepr (self // extension)) // extension
|
||||
resultRepr = self: (rRepr (self # extension)) # extension
|
||||
```
|
||||
|
||||
The second outer update ensures that the final result is also set to `a = 2`,
|
||||
and not only the `a` appearing in `b`.
|
||||
|
||||
Some details are left out, but this is the gist of it. See also [the Nix pill on
|
||||
overriding](https://nixos.org/guides/nix-pills/override-design-pattern.html) or
|
||||
[this article on fixpoints in Nix](http://r6.ca/blog/20140422T142911Z.html).
|
||||
overriding](https:#nixos.org/guides/nix-pills/override-design-pattern.html) or
|
||||
[this article on fixpoints in Nix](http:#r6.ca/blog/20140422T142911Z.html).
|
||||
|
||||
#### Limits
|
||||
|
||||
@ -195,14 +195,14 @@ overriding](https://nixos.org/guides/nix-pills/override-design-pattern.html) or
|
||||
a = {b = self.a.c;};
|
||||
}; in
|
||||
let extension = {a = {c = 2;};}; in
|
||||
rExt = let fixpoint = rRepr (fixpoint // extension); in
|
||||
fixpoint // extension
|
||||
rExt = let fixpoint = rRepr (fixpoint # extension); in
|
||||
fixpoint # extension
|
||||
# Gives {a = {c = 2;};} instead of expected {a = {b = 2; c = 2;};}
|
||||
```
|
||||
|
||||
### Nixpkgs overlays
|
||||
|
||||
[Overlays](https://nixos.wiki/wiki/Overlays) can be seen as a sequence of
|
||||
[Overlays](https:#nixos.wiki/wiki/Overlays) can be seen as a sequence of
|
||||
transformations from a base record, each layer having access to a `super`
|
||||
reference to the previous layer and the `self` reference to the final value.
|
||||
|
||||
@ -217,13 +217,13 @@ let overlay1 = self: super: {a = 1;}; in
|
||||
let overlay2 = self: super: {b = 1; a = super.a + 1;}; in
|
||||
let applyOverlays = self:
|
||||
let base = baseRepr self; in
|
||||
let first = base // overlay1 self base; in
|
||||
let second = first // overlay2 self first; in
|
||||
let first = base # overlay1 self base; in
|
||||
let second = first # overlay2 self first; in
|
||||
second; in
|
||||
let fixpoint = applyOverlays fixpoint; in fixpoint
|
||||
```
|
||||
|
||||
In practice, the `super // ..` and fixpoints parts can be factorised in
|
||||
In practice, the `super # ..` and fixpoints parts can be factorised in
|
||||
dedicated helper functions.
|
||||
|
||||
#### Advantages
|
||||
@ -243,7 +243,7 @@ dedicated helper functions.
|
||||
- ~~**(NEST)**~~ Overriding nested fields is still clumsy. For example, to
|
||||
override `lib.firefoxVersion`:
|
||||
```nix
|
||||
self: super: { lib = (super.lib or {}) // { firefoxVersion = ...; }; }
|
||||
self: super: { lib = (super.lib or {}) # { firefoxVersion = ...; }; }
|
||||
```
|
||||
|
||||
### NixOs module system
|
||||
@ -326,7 +326,7 @@ fields: {
|
||||
}
|
||||
```
|
||||
|
||||
Combined with [default values](https://cuelang.org/docs/tutorials/tour/types/defaults/),
|
||||
Combined with [default values](https:#cuelang.org/docs/tutorials/tour/types/defaults/),
|
||||
this provides an overriding mechanism:
|
||||
|
||||
```
|
||||
@ -378,7 +378,7 @@ local obj = {
|
||||
]
|
||||
```
|
||||
|
||||
This is similar to the Nix operator `//`, but doing recursive overriding in the
|
||||
This is similar to the Nix operator `#`, but doing recursive overriding in the
|
||||
expected way out of the box. The extension can access the previous version in
|
||||
the same way as Nixpkgs overlays, using the `super` keyword.
|
||||
|
||||
@ -408,18 +408,18 @@ strikingly resemble the semantics of objects and classes in OOP. Replace
|
||||
records with objects, fields with methods and overriding with inheritance. This
|
||||
is not so surprising: there's actually an history of encoding objects in
|
||||
functional languages as recursive records (see the introduction of
|
||||
[The Recursive Record Semantics of Objects Revisited](https://hal.inria.fr/inria-00072423)
|
||||
[The Recursive Record Semantics of Objects Revisited](https:#hal.inria.fr/inria-00072423)
|
||||
for a good overview) going back to 1988
|
||||
\[[Cardelli](http://lucacardelli.name/papers/inheritance.pdf)\].
|
||||
\[[Cardelli](http:#lucacardelli.name/papers/inheritance.pdf)\].
|
||||
|
||||
This is also mentioned in the
|
||||
[README](https://github.com/MuKnIO/nixpkgs/blob/devel/lib/pop.md#some-historical-context)
|
||||
[README](https:#github.com/MuKnIO/nixpkgs/blob/devel/lib/pop.md#some-historical-context)
|
||||
of POP (an object system in Nix), where the author observes that overriding
|
||||
mechanisms in Nix (and Jsonnet for that matter) are a simplified lazy object
|
||||
system (simplified because objects lack proper state and there is no distinction
|
||||
between classes and instances). Their logical conclusion is to embrace this fact
|
||||
and design a proper object system helped by existing literature, rather than
|
||||
reinventing the wheel. Similarly, Nix [overlays](https://nixos.wiki/wiki/Overlays)
|
||||
reinventing the wheel. Similarly, Nix [overlays](https:#nixos.wiki/wiki/Overlays)
|
||||
can be seen as a single inheritance mechanism.
|
||||
|
||||
Inheritance-based overriding imposes an order on the overrides. A single level
|
||||
@ -430,7 +430,7 @@ The NixOS module system is designed differently. It is based on merging: the
|
||||
configuration is created by combining a set of unordered records following
|
||||
specific rules. Of course, there's still a need for ordering information
|
||||
somewhere, but it is rather expressed as priorities. This system has the
|
||||
advantage of making merge commutative (in contrast with inheritance or the `//`
|
||||
advantage of making merge commutative (in contrast with inheritance or the `#`
|
||||
operator), as in CUE, and to untie data definition from precedence
|
||||
specification: one can define a module where each field has a different
|
||||
priority, if it makes sense to group them logically. With inheritance, values
|
||||
@ -486,7 +486,7 @@ r = {
|
||||
a = 1;
|
||||
b = a + 1;
|
||||
}
|
||||
// Definition of the representation of r
|
||||
# Definition of the representation of r
|
||||
repr(r) := fun self => {
|
||||
a = 1;
|
||||
b = self.a + 1;
|
||||
@ -550,7 +550,7 @@ let block2 = {
|
||||
path = ["/bin"]
|
||||
} in
|
||||
|
||||
// { path = ["usr/local/bin", "/bin"] }
|
||||
# { path = ["usr/local/bin", "/bin"] }
|
||||
block1 & block2
|
||||
```
|
||||
|
||||
@ -584,7 +584,7 @@ expression again.
|
||||
|
||||
There's more potential optimizations, but this first step should be a reasonable
|
||||
trade-off between implementation complexity and performance. See
|
||||
[#103](https://github.com/tweag/nickel/issues/103) for more details.
|
||||
[#103](https:#github.com/tweag/nickel/issues/103) for more details.
|
||||
|
||||
### Scoping
|
||||
|
||||
@ -640,7 +640,7 @@ Example:
|
||||
| default = 1,
|
||||
|
||||
bar | Str,
|
||||
//equivalent to `bar | Str | priority 0`
|
||||
#equivalent to `bar | Str | priority 0`
|
||||
|
||||
baz.boo.bor | priority -4 = "value",
|
||||
|
||||
@ -650,13 +650,13 @@ Example:
|
||||
|
||||
#### Recursive priorities
|
||||
|
||||
As noted in [#240](https://github.com/tweag/nickel/issues/240), configurations
|
||||
As noted in [#240](https:#github.com/tweag/nickel/issues/240), configurations
|
||||
should be easily overridable, and the approach outlined until now can end up
|
||||
annoyingly requiring configurations to be written with either `default` or
|
||||
`force` everywhere.
|
||||
|
||||
This RFC proposes to add *recursive* (or "leafy", or "push down") priorities, as
|
||||
described in [#279](https://github.com/tweag/nickel/issues/279). We define the
|
||||
described in [#279](https:#github.com/tweag/nickel/issues/279). We define the
|
||||
new meta-values `default rec` and `force rec`, whose semantics are defined as:
|
||||
|
||||
- `eval(expr | default rec)`: case of `eval(expr)`:
|
||||
@ -688,31 +688,31 @@ let neutralConf = {
|
||||
}
|
||||
|
||||
let defaulted | default rec = neutralConf
|
||||
// ^ Will evaluate to:
|
||||
// {
|
||||
// foo | default = 1,
|
||||
// bar = {
|
||||
// baz | default = "stuff",
|
||||
// bar.blorg | default = false,
|
||||
// },
|
||||
// }
|
||||
// This is different from `neutralConf | default`! The latter version
|
||||
// would be overrided at once, as illustrated below.
|
||||
# ^ Will evaluate to:
|
||||
# {
|
||||
# foo | default = 1,
|
||||
# bar = {
|
||||
# baz | default = "stuff",
|
||||
# bar.blorg | default = false,
|
||||
# },
|
||||
# }
|
||||
# This is different from `neutralConf | default`! The latter version
|
||||
# would be overrided at once, as illustrated below.
|
||||
|
||||
defaulted & {bar.baz = "shapoinkl"}
|
||||
// ^ Gives the expected:
|
||||
// {
|
||||
// foo | default = 1,
|
||||
// bar = {
|
||||
// baz = "shapoinkl";
|
||||
// bar.blor | default = false,
|
||||
// },
|
||||
// }
|
||||
// While
|
||||
# ^ Gives the expected:
|
||||
# {
|
||||
# foo | default = 1,
|
||||
# bar = {
|
||||
# baz = "shapoinkl";
|
||||
# bar.blor | default = false,
|
||||
# },
|
||||
# }
|
||||
# While
|
||||
|
||||
(neutralConf | default) & {bar.baz = "shapoinkl"}
|
||||
// ^ This gives only:
|
||||
// {bar.baz = "shapoinkl"}
|
||||
# ^ This gives only:
|
||||
# {bar.baz = "shapoinkl"}
|
||||
```
|
||||
|
||||
This way, an existing definition (arbitrarily complex: that could be the root of
|
||||
@ -734,7 +734,7 @@ definition:
|
||||
|
||||
```nickel
|
||||
let add = fun args => args.lower + args.higher in
|
||||
// {a = 3}
|
||||
# {a = 3}
|
||||
{a | merge add = 1} & {a = 1} & {a = 1}
|
||||
```
|
||||
|
||||
@ -746,10 +746,10 @@ let r1 = {a = 1} in
|
||||
let r2 = {a = 1} in
|
||||
let r3 = {a | merge add = 1} in
|
||||
|
||||
// {val = 2}
|
||||
# {val = 2}
|
||||
r1 & r2 & r3
|
||||
|
||||
// {val = 3}
|
||||
# {val = 3}
|
||||
r3 & r1 & r2
|
||||
```
|
||||
|
||||
@ -770,7 +770,7 @@ Possible solutions:
|
||||
val1 & val2 & (val3 | merge func)
|
||||
<=> (val1 | merge func) & val2 & val3
|
||||
<=> func (func val1 val2) val3
|
||||
// instead of the naive
|
||||
# instead of the naive
|
||||
<=/=> func (val1 & val2) val3
|
||||
```
|
||||
|
||||
@ -824,17 +824,17 @@ questions:
|
||||
let add = fun x y => x + y in
|
||||
|
||||
let var = 1 & 1 in
|
||||
var & (1 | merge add) // result?
|
||||
(var | merge add) & 1 // result?
|
||||
var & (1 | merge add) # result?
|
||||
(var | merge add) & 1 # result?
|
||||
|
||||
((1 & 1) + (1 & 1)) & (1 | merge add) // result?
|
||||
((1 & 1) + (1 & 1) | merge add) & 1 // result?
|
||||
((1 & 1) + (1 & 1)) & (1 | merge add) # result?
|
||||
((1 & 1) + (1 & 1) | merge add) & 1 # result?
|
||||
|
||||
// file: somefile.ncl
|
||||
# file: somefile.ncl
|
||||
1 & 1
|
||||
// file: other.ncl
|
||||
(import "somefile") & (1 | merge add) // result?
|
||||
(import "somefile" | merge add) & 1 // result?
|
||||
# file: other.ncl
|
||||
(import "somefile") & (1 | merge add) # result?
|
||||
(import "somefile" | merge add) & 1 # result?
|
||||
```
|
||||
|
||||
In the following, we will write "meta"-code (think of the code of the Nickel
|
||||
@ -857,7 +857,7 @@ data Metadata = Metadata {
|
||||
priority :: Priority,
|
||||
merge :: Option Priority,
|
||||
contracts :: Option (List Contract),
|
||||
// ...
|
||||
# ...
|
||||
}
|
||||
|
||||
data AbsMergeTree a =
|
||||
@ -873,7 +873,7 @@ expressions, such that `let x = a & b in x & c` and `a & b & c` has the same
|
||||
merge tree: that is, merge treee commute with evaluation.
|
||||
|
||||
```
|
||||
// we maintain an environment of bindings in `env`
|
||||
# we maintain an environment of bindings in `env`
|
||||
|
||||
metaData [| e | attr = val, ... |] ::=
|
||||
{ attr = val, ... } if priority is set
|
||||
@ -883,21 +883,21 @@ mergeTree e ::= (absMergeTree e, metaData e)
|
||||
|
||||
absMergeTree [| e1 & e2 |] = Merge(mergeTree(e1), mergeTree(e2))
|
||||
|
||||
// whnf = Weak head normal form, result of evaluation
|
||||
# whnf = Weak head normal form, result of evaluation
|
||||
absMergeTree [| whnf |] @ e = Exp(e)
|
||||
|
||||
// Should mergeTree cross import boundaries? Probably not
|
||||
# Should mergeTree cross import boundaries? Probably not
|
||||
absMergeTree [| import path |] @ e = Exp(e)
|
||||
|
||||
// All other cases
|
||||
# All other cases
|
||||
absMergeTree e = weakEval e
|
||||
|
||||
// weakEval is defined exactly as standard evaluation, excepted that it stops at
|
||||
// merge expressions, as if they were a lazy datatype in weak head normal form
|
||||
# weakEval is defined exactly as standard evaluation, excepted that it stops at
|
||||
# merge expressions, as if they were a lazy datatype in weak head normal form
|
||||
|
||||
weakEval [| e1 & e2 |] @ e = e
|
||||
|
||||
// all other cases are defined exactly as for eval
|
||||
# all other cases are defined exactly as for eval
|
||||
weakEval e = ...
|
||||
```
|
||||
|
||||
@ -920,7 +920,7 @@ extractMergeFuns (Leaf(e,meta)) = [f] if meta.merge == Some([| f |])
|
||||
mergeFunction : MergeTree -> Result MergeFunction ()
|
||||
mergeFunction t = let funs = extractMergeFuns t in
|
||||
if lists.length t == 0 then
|
||||
Ok(__builtinMerge) // the standard `&` merge function
|
||||
Ok(__builtinMerge) # the standard `&` merge function
|
||||
else if lists.length t == 1 then
|
||||
Ok(head t)
|
||||
else
|
||||
@ -930,7 +930,7 @@ mergeFunction t = let funs = extractMergeFuns t in
|
||||
And the interpretation of a merge tree by a merge function:
|
||||
|
||||
```
|
||||
// can be extended, as long as it is a partially ordered set
|
||||
# can be extended, as long as it is a partially ordered set
|
||||
Priority = Number | -inf | +inf | ...
|
||||
|
||||
interpret (Merge(ast_1,meta_1),Merge(ast_2,meta_2)) f =
|
||||
|
@ -189,7 +189,7 @@ clarification:
|
||||
to take it into account. But this implies giving semantics to things like
|
||||
`record.is_empty { ; a}` or `{ ; a} & { ; b}`. Those are actually
|
||||
interesting questions outside of the context of this RFC (see
|
||||
[201](https://github.com/tweag/nickel/issues/201)).
|
||||
[201](https:#github.com/tweag/nickel/issues/201)).
|
||||
|
||||
While allowing tails in arbitrary record may look more uniform, it requires
|
||||
more design thinking. There isn't any obvious practical usage that seems to
|
||||
@ -434,9 +434,9 @@ idea from the [Record types](#record-types) section:
|
||||
```nickel
|
||||
let Contract = {foo | OtherContr, bar | Str} in
|
||||
let my_value | Contract = {...} in
|
||||
// Would typecheck, as my_value has type Contract, which would be expanded to
|
||||
// {foo | OtherContr, bar | Str} and statically extracted as {foo: OtherContr, bar:
|
||||
// Str}
|
||||
# Would typecheck, as my_value has type Contract, which would be expanded to
|
||||
# {foo | OtherContr, bar | Str} and statically extracted as {foo: OtherContr, bar:
|
||||
# Str}
|
||||
let appendToBar : Str -> Str = fun s => my_value.bar ++ s
|
||||
```
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user