From a86d3c98168c203f07733d10387ff5adc6567261 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Wed, 20 Apr 2022 18:14:30 +0200 Subject: [PATCH 01/20] WIP --- rfcs/001-overriding.md | 2 +- rfcs/003-nix-nickel.md | 74 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 rfcs/003-nix-nickel.md diff --git a/rfcs/001-overriding.md b/rfcs/001-overriding.md index 3299db31c3..a74d51b742 100644 --- a/rfcs/001-overriding.md +++ b/rfcs/001-overriding.md @@ -898,7 +898,7 @@ absMergeTree e = weakEval e weakEval [| e1 & e2 |] @ e = e // all other cases are defined exactly as for eval -weakEval e = ... +weakEval e = ... ``` ### Semantics diff --git a/rfcs/003-nix-nickel.md b/rfcs/003-nix-nickel.md new file mode 100644 index 0000000000..e575a7c401 --- /dev/null +++ b/rfcs/003-nix-nickel.md @@ -0,0 +1,74 @@ +--- +feature: nix-nickel +start-date: 2022-01-12 +author: Yann Hamdaoui +--- + +Nix-Nickel +========== + +The goal of the present RFC is to lay down a compelling and complete plan to +integrate Nix and Nickel. More precisely, the goal is to be able to use Nickel +as a front-end language for writing Nix expressions in a seamless way -- or, a +minima, that there is no aspect in which using Nickel instead of Nix expressions +is unreasonably worse, while Nickel will hopefully fare better than Nix in +others. + +This document is structured as follows: + +1. [Motivation](#motivation): exposes the central motivations and gives very + concrete example of what we want to achieve. +2. [Challenges](#challenges): identifies the technical challenges, as of today, + which arise when trying to achieve the goals. +3. [Proposal](#proposal): a concrete proposal to overcome the challenges and + achieve the goals. + +# Motivation + +Nickel is a general-purpose configuration language. Yet, Nix is one particularly +important target, as it is one of the motivation for creating Nickel in the +first place. Now that the first release is out and the core language has been +stabilized, the next step is to offer a compelling experience for using Nickel +in place of Nix as a language to write packages, flakes and modules. + +## Constraints + +- do not require unreasonable changes to Nix itself. +- implementable in a reasonable time, if possible incrementally. + +## Scope + +Packages, flakes, modules? + +Packages: because the other aspects of Nix heavily rely on derivations, anyway. +Thus, derivations are foundational: it's hard to imagine having a good story for +the others before having one for derivations. What's more, the derivation story +is just lower-level, and simpler. It's frequent to write derivations as a part +of a module, even in something as simple as + +```nix +systemd.myUnit.UnitConfig.ExecStart = pkgs.writeScript "foo" "..." +``` + +## Nix-Nickel fantasised + +Examples of using Nickel for Nix, in practice. What we would want. + +# Challenges + +## Interactions + +Leveraging Nixpkgs and Nixosmodules (probably) require a bidirectional +communication between the two. + +## String contexts + +## Modules + +# Proposal + +## Interaction + +Compile Nix to Nickel. + +## String Context From 2b718101725462275ba08b24a6bebd597f6f6490 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Thu, 21 Apr 2022 15:03:19 +0200 Subject: [PATCH 02/20] WIP --- rfcs/003-nix-nickel.md | 89 ++++++++++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 33 deletions(-) diff --git a/rfcs/003-nix-nickel.md b/rfcs/003-nix-nickel.md index e575a7c401..2a42d9345d 100644 --- a/rfcs/003-nix-nickel.md +++ b/rfcs/003-nix-nickel.md @@ -4,47 +4,70 @@ start-date: 2022-01-12 author: Yann Hamdaoui --- -Nix-Nickel -========== +# Nix-Nickel -The goal of the present RFC is to lay down a compelling and complete plan to -integrate Nix and Nickel. More precisely, the goal is to be able to use Nickel -as a front-end language for writing Nix expressions in a seamless way -- or, a -minima, that there is no aspect in which using Nickel instead of Nix expressions -is unreasonably worse, while Nickel will hopefully fare better than Nix in -others. +The goal of the present RFC is to elaborate a compelling plan to integrate Nix +and Nickel. We want to use Nickel as a front-end language for writing Nix +expressions in a seamless way -- or, at least, such that there is no aspect in +which Nickel is unreasonably worse than Nix expressions. Hopefully, Nickel will +hopefully fare better than Nix in other aspects. This document is structured as follows: -1. [Motivation](#motivation): exposes the central motivations and gives very - concrete example of what we want to achieve. +1. [Motivation](#motivation): exposes the motivation for and the scope of this + RFC, and gives concrete examples of what we want to achieve. 2. [Challenges](#challenges): identifies the technical challenges, as of today, - which arise when trying to achieve the goals. -3. [Proposal](#proposal): a concrete proposal to overcome the challenges and - achieve the goals. + raised by the goals defined in 1. +3. [Proposal](#proposal): presents concrete proposals to overcome those challenges + and achieve the goals. -# Motivation +## Motivation -Nickel is a general-purpose configuration language. Yet, Nix is one particularly -important target, as it is one of the motivation for creating Nickel in the -first place. Now that the first release is out and the core language has been +Nickel is a general-purpose configuration language. Yet, Nix is one very special +use-case. Nix was one of the motivation for creating Nickel in the first place. +Now that Nickel has been released, and the core language has been relatively stabilized, the next step is to offer a compelling experience for using Nickel in place of Nix as a language to write packages, flakes and modules. -## Constraints - -- do not require unreasonable changes to Nix itself. -- implementable in a reasonable time, if possible incrementally. - -## Scope - -Packages, flakes, modules? +### Constraints + +Because Nix is a distinct project from Nickel which has existed for quite +some time now, we need operate under the following constraints: + +- _Do not require unreasonable changes to Nix itself_. While it's probably + illusory to avoid any change in Nix at all, we must strive to choose a + solution that keeps them reasonable and as undisturbing as possible for + regular Nix users, at least in the mid term. +- _Implementable in a reasonable time, if possible incrementally_. This RFC aims + at proposing a solid vision and not a temporary work-around. That being + said, the development bandwidth of the Nickel team is finite, and the + solution should be reasonably implementable in the order of magnitude that + stay under a year(??). +- _Backward-compatible_. Related to the previous point, we will realistically + need to be able to leverage existing Nix code in some way: otherwise, even + in the (unlikely) best case scenario where everybody suddenly switches to + Nickel right away, it'll take up a very long time to migrate everything. +- _Do not lock ourselves in the current Nix/Nixpkgs architecture_. Nixpkgs had + made a number of design choices that are, in retrospective, not optimal + (stdenv, describing packages as functions instead of data, etc.). While we + have to keep a form of backward-compatibility, we want to also avoid locking + ourselves by instilling some of those questionable design choices into this + proposal. In other word, this document must propose a solution that stay + compatible with radically departing from those choices. + +### Scope + +There a several way of using Nix, and in particular in writing Nix expressions: + +- Nixpkgs: derivations and packages in the original style +- Flakes: Stand-alone, composable packages +- NixOS modules: system configuration modules Packages: because the other aspects of Nix heavily rely on derivations, anyway. Thus, derivations are foundational: it's hard to imagine having a good story for the others before having one for derivations. What's more, the derivation story is just lower-level, and simpler. It's frequent to write derivations as a part -of a module, even in something as simple as +of a module, even in something as simple as ```nix systemd.myUnit.UnitConfig.ExecStart = pkgs.writeScript "foo" "..." @@ -54,21 +77,21 @@ systemd.myUnit.UnitConfig.ExecStart = pkgs.writeScript "foo" "..." Examples of using Nickel for Nix, in practice. What we would want. -# Challenges +## Challenges -## Interactions +### Interactions Leveraging Nixpkgs and Nixosmodules (probably) require a bidirectional communication between the two. -## String contexts +### String contexts -## Modules +### Modules -# Proposal +## Proposal -## Interaction +### Interaction Compile Nix to Nickel. -## String Context +### String Context From 4d66e5451db2c25aac52bc1ec6682e15c5cf2909 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Thu, 21 Apr 2022 15:15:31 +0200 Subject: [PATCH 03/20] WIP --- rfcs/003-nix-nickel.md | 1 + 1 file changed, 1 insertion(+) diff --git a/rfcs/003-nix-nickel.md b/rfcs/003-nix-nickel.md index 2a42d9345d..559828e9f1 100644 --- a/rfcs/003-nix-nickel.md +++ b/rfcs/003-nix-nickel.md @@ -63,6 +63,7 @@ There a several way of using Nix, and in particular in writing Nix expressions: - Flakes: Stand-alone, composable packages - NixOS modules: system configuration modules +We aims at handling all of those cases Packages: because the other aspects of Nix heavily rely on derivations, anyway. Thus, derivations are foundational: it's hard to imagine having a good story for the others before having one for derivations. What's more, the derivation story From f61ce0c9359f62a39c1b00887575299c7118738a Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Fri, 22 Apr 2022 18:51:01 +0200 Subject: [PATCH 04/20] WIP --- rfcs/003-nix-nickel.md | 162 ++++++++++++++++++++++++++++++----------- 1 file changed, 118 insertions(+), 44 deletions(-) diff --git a/rfcs/003-nix-nickel.md b/rfcs/003-nix-nickel.md index 559828e9f1..66845c458b 100644 --- a/rfcs/003-nix-nickel.md +++ b/rfcs/003-nix-nickel.md @@ -6,28 +6,27 @@ author: Yann Hamdaoui # Nix-Nickel -The goal of the present RFC is to elaborate a compelling plan to integrate Nix -and Nickel. We want to use Nickel as a front-end language for writing Nix +The goal of the present RFC is to lay out a compelling plan to integrate Nix and +Nickel. We want to use Nickel as a front-end language for writing Nix expressions in a seamless way -- or, at least, such that there is no aspect in -which Nickel is unreasonably worse than Nix expressions. Hopefully, Nickel will -hopefully fare better than Nix in other aspects. +which Nickel is unreasonably worse than Nix expressions, and is hopefully better in others. This document is structured as follows: -1. [Motivation](#motivation): exposes the motivation for and the scope of this - RFC, and gives concrete examples of what we want to achieve. +1. [Motivation](#motivation): exposes the motivation and the scope of this RFC, + and gives concrete examples of what we want to achieve. 2. [Challenges](#challenges): identifies the technical challenges, as of today, raised by the goals defined in 1. -3. [Proposal](#proposal): presents concrete proposals to overcome those challenges - and achieve the goals. +3. [Proposal](#proposal): gives concrete proposals to overcome those challenges + and achieve the goals of 1. ## Motivation Nickel is a general-purpose configuration language. Yet, Nix is one very special -use-case. Nix was one of the motivation for creating Nickel in the first place. -Now that Nickel has been released, and the core language has been relatively -stabilized, the next step is to offer a compelling experience for using Nickel -in place of Nix as a language to write packages, flakes and modules. +use-case. Nix has been the main motivation for creating Nickel in the first +place. Now that Nickel has been released, the next step is to offer a compelling +experience for using Nickel in place of Nix as a language to write packages, +flakes and modules. ### Constraints @@ -35,64 +34,139 @@ Because Nix is a distinct project from Nickel which has existed for quite some time now, we need operate under the following constraints: - _Do not require unreasonable changes to Nix itself_. While it's probably - illusory to avoid any change in Nix at all, we must strive to choose a - solution that keeps them reasonable and as undisturbing as possible for - regular Nix users, at least in the mid term. + illusory to avoid any change in Nix at all, we must strive to keep them + reasonably small and undisturbing for regular Nix users, at least in the + middle term. - _Implementable in a reasonable time, if possible incrementally_. This RFC aims - at proposing a solid vision and not a temporary work-around. That being - said, the development bandwidth of the Nickel team is finite, and the - solution should be reasonably implementable in the order of magnitude that - stay under a year(??). + at proposing a solid vision for the future and not a temporary work-around. + Still, given the finite bandwidth of the Nickel team, the solution should be + implementable in limited time (in the order of magnitude months, up to half + a year). If possible, the plan should have intermediate milestones enabling + new features in a incremental way. - _Backward-compatible_. Related to the previous point, we will realistically - need to be able to leverage existing Nix code in some way: otherwise, even - in the (unlikely) best case scenario where everybody suddenly switches to - Nickel right away, it'll take up a very long time to migrate everything. -- _Do not lock ourselves in the current Nix/Nixpkgs architecture_. Nixpkgs had + need to be able to leverage existing Nix code (especially from Nixpkgs) in + some way: otherwise, even in the (unlikely) best case scenario where + everybody suddenly switches to Nickel right away, it'll take up a very long + time to migrate everything. +- _Do not lock ourselves in the current Nixpkgs architecture_. Nixpkgs had made a number of design choices that are, in retrospective, not optimal (stdenv, describing packages as functions instead of data, etc.). While we - have to keep a form of backward-compatibility, we want to also avoid locking - ourselves by instilling some of those questionable design choices into this - proposal. In other word, this document must propose a solution that stay - compatible with radically departing from those choices. + have to keep a form of backward-compatibility, we want to do so while + avoiding to tie ourselves to the current design and implementation. In other + words, this document must propose a solution that stay compatible with + radically departing from those choices. ### Scope -There a several way of using Nix, and in particular in writing Nix expressions: +Nix expressions are used to several related but different ends, which come with +varying goals and constraints: -- Nixpkgs: derivations and packages in the original style -- Flakes: Stand-alone, composable packages -- NixOS modules: system configuration modules +- Nixpkgs: derivations and packages in the original style. +- Flakes: new format for stand-alone, decentralized and composable packages. +- NixOS modules: system configuration. -We aims at handling all of those cases -Packages: because the other aspects of Nix heavily rely on derivations, anyway. -Thus, derivations are foundational: it's hard to imagine having a good story for -the others before having one for derivations. What's more, the derivation story -is just lower-level, and simpler. It's frequent to write derivations as a part -of a module, even in something as simple as +We aims at handling all those cases in the long term, but the scope of such an +undertaking appears too large for a single RFC. We decide to focus on the first +item of the list: writing derivations and Nixpkgs-style packages. Indeed, a +derivation is the foundational concept on which the whole Nix model is based. +It's thus hard to imagine having a good story for using Nickel for NixOS or for +flakes without having one for derivations. It's frequent to use derivations as a +part of e.g. a NixOS module: ```nix systemd.myUnit.UnitConfig.ExecStart = pkgs.writeScript "foo" "..." ``` -## Nix-Nickel fantasised +Using `pkgs.writeScript` in an hypothetical NixOS Nickel module implies to know +how to generate derivations. -Examples of using Nickel for Nix, in practice. What we would want. +Derivations are in some sense "lower-level" than the other Nix concepts, which +build on it. It makes sense to tackle derivations first. From there, Nixpkgs is +probably the thinnest layer over derivations, compared to flake or the NixOS +module system. + +## Nix-Nickel fantasised + +Examples of using Nickel for Nix, in practice. What we want. ## Challenges -### Interactions +### Interaction with Nixpkgs + +There a bunch of actions that require leveraging Nixpkgs: + +- **USE**: Using a package from Nixpkgs, for example as a dependency +- **LIB**: Use one of the myriad of helpers from Nixpkgs: `mkShell`, + `mkDerivation`, `writeText`, etc. +- **OVERRIDE**: Use a package from Nixpkgs but overriding some of its parameters -Leveraging Nixpkgs and Nixosmodules (probably) require a bidirectional -communication between the two. +**USE** could be done in a simple way, by just passing around derivations from +Nix to Nickel. Derivations are just fully evaluated data, that can be encoded as +e.g. JSON. + +**LIB** is more involved. Some of those helpers take either functions or lazy +data as parameters, and an interface (in the form of a FFI) between Nix and +Nickel would needs to handle those back and forth at the boundary of the two +languages transparently. + +**OVERRIDE** is in practice technically the same issue as **LIB**, since it +amounts to calling Nix functions `override`, `overrideAttrs`, etc. That is, +solving **LIB** automatically solves **OVERRIDE**. ### String contexts -### Modules +[String +contexts](https://shealevy.com/blog/2018/08/05/understanding-nixs-string-context/) +are a useful feature of Nix. In a Nix program, each string carries a context +recording which other variables it depends on at runtime, via string +interpolation. Nix can then automatically determine the dependencies of a +derivation. ## Proposal ### Interaction -Compile Nix to Nickel. +As underlined in [Interaction with Nixpkgs](#interaction-with-nixpkgs), an +FFI-like interaction would imply transparent bidirectional communication between +Nix and Nickel. This is made very hard by the nature of both languages, which +are lazy and functional, implying that a Nix function called from Nickel may +incur calls back to Nickel arbitrarily (and vice-versa) to apply function or to +evaluate thunks. Not only this looks complex, but maintaining reasonable +efficiency seems to be a challenge too. + +Another possibility is to have everything evaluate in only one world, either Nix +and Nickel. Practically, we could either compile the Nix code to Nickel, or the +Nickel code to Nix, and run everything there. Compiling Nickel to Nix and run +everything on the Nix side would have the advantage of leveraging all the Nix +primitives and machinery freely. However, this solution simply nullifies a lot +of the value proposition of Nickel. Compiling Nickel to Nix is not trivial, and +would loose the native support for contracts (and thus good error reporting), +overriding, and so on, going back to potential degraded performance and bad +error messages. + +On the other hand, compiling Nix to Nickel appears simpler on paper. Nickel is +close to being a superset of Nix (the only non-trivial missing features being +the `with` construct and string contexts). This would preserve the Nickel +advances, and we could leverage directly any non-builtin Nix function, including +all the Nixpkgs library functions. + +The possible drawbacks of this approach: + - Performance + - On the fly, or not + - dynamic imports and typechecking + +We will also need to reimplement Nix builtins in Nickel (the compat layer). This +is an interesting milestone, because even without the compiler, this will allow +to write derivation in Pure Nickel. ### String Context + +- Overloaded string constructors. + * Have a specific string delimiter `nix%"foo %{pkgs.hello}"%` + * Have a corresponding contract or builtin to enforce Nix strings everywhere. + * Use those in Nickel derivations. That means people will have errors with + standard strings. + +The question is how to make this process not Nix-specific but more general. Look +at g-expr in Guix (a bit of the same idea in the end?). Should the extension be +specified in Nickel itself, or as an interpreter plugin? From f09c33c78fb67d18fb70a5b48f782c878a3d5aee Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 25 Apr 2022 13:09:21 +0200 Subject: [PATCH 05/20] WIP --- rfcs/003-nix-nickel.md | 122 +++++++++++++++++++++-------------------- 1 file changed, 63 insertions(+), 59 deletions(-) diff --git a/rfcs/003-nix-nickel.md b/rfcs/003-nix-nickel.md index 66845c458b..f9b7ed5b4a 100644 --- a/rfcs/003-nix-nickel.md +++ b/rfcs/003-nix-nickel.md @@ -9,7 +9,8 @@ author: Yann Hamdaoui The goal of the present RFC is to lay out a compelling plan to integrate Nix and Nickel. We want to use Nickel as a front-end language for writing Nix expressions in a seamless way -- or, at least, such that there is no aspect in -which Nickel is unreasonably worse than Nix expressions, and is hopefully better in others. +which Nickel is unreasonably worse than Nix expressions (and will be hopefully +better in others). This document is structured as follows: @@ -17,8 +18,8 @@ This document is structured as follows: and gives concrete examples of what we want to achieve. 2. [Challenges](#challenges): identifies the technical challenges, as of today, raised by the goals defined in 1. -3. [Proposal](#proposal): gives concrete proposals to overcome those challenges - and achieve the goals of 1. +3. [Proposal](#proposal): provides concrete proposals to overcome those + challenges and achieve the goals of 1. ## Motivation @@ -36,54 +37,52 @@ some time now, we need operate under the following constraints: - _Do not require unreasonable changes to Nix itself_. While it's probably illusory to avoid any change in Nix at all, we must strive to keep them reasonably small and undisturbing for regular Nix users, at least in the - middle term. -- _Implementable in a reasonable time, if possible incrementally_. This RFC aims - at proposing a solid vision for the future and not a temporary work-around. - Still, given the finite bandwidth of the Nickel team, the solution should be - implementable in limited time (in the order of magnitude months, up to half - a year). If possible, the plan should have intermediate milestones enabling - new features in a incremental way. -- _Backward-compatible_. Related to the previous point, we will realistically - need to be able to leverage existing Nix code (especially from Nixpkgs) in - some way: otherwise, even in the (unlikely) best case scenario where - everybody suddenly switches to Nickel right away, it'll take up a very long - time to migrate everything. + medium term. +- _Implementable in a reasonable timeline, if possible incrementally_. This RFC + aims at proposing a solid vision for the future and not a temporary + work-around. Still, given the finite bandwidth of the Nickel team, the + proposed solution should be implementable in limited time (in the order of + magnitude months, up to half a year). If possible, the plan should have + intermediate milestones enabling new capabilities in an incremental way. +- _Backward-compatible_. We will realistically need to be able to leverage + existing Nix code (especially from Nixpkgs) in some way: otherwise, even in + the unlikely best case scenario where everybody suddenly switches to Nickel + right away, it'll still take up a very long time to migrate everything. - _Do not lock ourselves in the current Nixpkgs architecture_. Nixpkgs had - made a number of design choices that are, in retrospective, not optimal - (stdenv, describing packages as functions instead of data, etc.). While we - have to keep a form of backward-compatibility, we want to do so while - avoiding to tie ourselves to the current design and implementation. In other - words, this document must propose a solution that stay compatible with - radically departing from those choices. + made a number of design choices that are, in hindsight, not optimal (stdenv, + describing packages as functions instead of data, etc.). While we have to + keep some form of backward-compatibility, we want to do so while avoiding to + tie ourselves to the current design and implementation. In other words, this + document must propose a solution that stay compatible with a future radical + departure from e.g. the Nixpkgs architecture. ### Scope -Nix expressions are used to several related but different ends, which come with -varying goals and constraints: +Nix expressions are used in related but different ways, each coming with varying +goals and constraints: - Nixpkgs: derivations and packages in the original style. -- Flakes: new format for stand-alone, decentralized and composable packages. -- NixOS modules: system configuration. +- Flakes: new format for decentralized and composable packages. +- NixOS modules: NixOS system configuration. -We aims at handling all those cases in the long term, but the scope of such an -undertaking appears too large for a single RFC. We decide to focus on the first -item of the list: writing derivations and Nixpkgs-style packages. Indeed, a -derivation is the foundational concept on which the whole Nix model is based. -It's thus hard to imagine having a good story for using Nickel for NixOS or for -flakes without having one for derivations. It's frequent to use derivations as a -part of e.g. a NixOS module: +In the long term, we aims at handling all those cases, but the scope of such an +undertaking appears very large for a single RFC. We decide to focus on the first +item: writing derivations and Nixpkgs-style packages. + +The rationale is that derivations are the foundational concept on which the +whole Nix model is based. Flakes and NixOS modules build on derivations, as +illustrated by the following excerpt from a NixOS module: ```nix systemd.myUnit.UnitConfig.ExecStart = pkgs.writeScript "foo" "..." ``` -Using `pkgs.writeScript` in an hypothetical NixOS Nickel module implies to know -how to generate derivations. +Something as innocent as using `pkgs.writeScript` in an hypothetical NixOS +Nickel module already implies to know how to generate derivations. -Derivations are in some sense "lower-level" than the other Nix concepts, which -build on it. It makes sense to tackle derivations first. From there, Nixpkgs is -probably the thinnest layer over derivations, compared to flake or the NixOS -module system. +Thus, tackling derivations first makes sense. From there, Nixpkgs is probably +the thinnest layer over derivations of the three, and already open a lot of +possibilities, so we choose to include it as well. ## Nix-Nickel fantasised @@ -95,43 +94,48 @@ Examples of using Nickel for Nix, in practice. What we want. There a bunch of actions that require leveraging Nixpkgs: -- **USE**: Using a package from Nixpkgs, for example as a dependency +- **PKG**: Using a package from Nixpkgs, for example as a dependency - **LIB**: Use one of the myriad of helpers from Nixpkgs: `mkShell`, `mkDerivation`, `writeText`, etc. -- **OVERRIDE**: Use a package from Nixpkgs but overriding some of its parameters +- **OVD**: Use a package from Nixpkgs but overriding some of its parameters -**USE** could be done in a simple way, by just passing around derivations from -Nix to Nickel. Derivations are just fully evaluated data, that can be encoded as -e.g. JSON. +**PKG** could be done in a simple way, by just passing around derivations from +Nix to Nickel. Derivations are fully evaluated data that can be encoded as e.g. +JSON. -**LIB** is more involved. Some of those helpers take either functions or lazy -data as parameters, and an interface (in the form of a FFI) between Nix and -Nickel would needs to handle those back and forth at the boundary of the two -languages transparently. +**LIB** is more involved. Some of Nixpkgs helpers take either functions, but the +sole laziness of expressions means that an interface (in the form of a FFI) +between Nix and Nickel would needs to handle going back and forth between the +two languages transparently. -**OVERRIDE** is in practice technically the same issue as **LIB**, since it -amounts to calling Nix functions `override`, `overrideAttrs`, etc. That is, -solving **LIB** automatically solves **OVERRIDE**. +**OVD** is technically not very different than **LIB**, since it mostly amounts +to calling Nix functions like `override`, `overrideAttrs`, etc (for any override +that doesn't operate directly on a derivation, which most are). That is, solving +**LIB** would solve **OVD** as well. ### String contexts [String contexts](https://shealevy.com/blog/2018/08/05/understanding-nixs-string-context/) -are a useful feature of Nix. In a Nix program, each string carries a context -recording which other variables it depends on at runtime, via string -interpolation. Nix can then automatically determine the dependencies of a -derivation. +is a very useful feature of Nix. In a Nix program, each string carries a context +recording which other derivations it depends on at runtime, by tracking +variables usage inside string interpolation. Nix can then automatically +determine the dependencies of a derivation. + +Abandoning the automatic dependency management offered by string contexts in +Nickel sounds like a degradation of developers' quality of life that is hard to +justify. ## Proposal ### Interaction As underlined in [Interaction with Nixpkgs](#interaction-with-nixpkgs), an -FFI-like interaction would imply transparent bidirectional communication between -Nix and Nickel. This is made very hard by the nature of both languages, which -are lazy and functional, implying that a Nix function called from Nickel may -incur calls back to Nickel arbitrarily (and vice-versa) to apply function or to -evaluate thunks. Not only this looks complex, but maintaining reasonable +FFI-like interaction would require a transparent bidirectional communication +between Nix and Nickel. This is made very hard by the nature of both languages, +which are lazy and functional, meaning that a Nix function called from Nickel +may incur arbitrary calls back to Nickel (and vice-versa) to apply a function or +to evaluate a thunk. Not only does this looks complex, but maintaining reasonable efficiency seems to be a challenge too. Another possibility is to have everything evaluate in only one world, either Nix From 619438a36a2a1d2c86d48886c420effd6d634f8c Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 25 Apr 2022 18:31:01 +0200 Subject: [PATCH 06/20] WIP --- rfcs/003-nix-nickel.md | 128 ++++++++++++++++++++++++++++------------- 1 file changed, 88 insertions(+), 40 deletions(-) diff --git a/rfcs/003-nix-nickel.md b/rfcs/003-nix-nickel.md index f9b7ed5b4a..39619034f0 100644 --- a/rfcs/003-nix-nickel.md +++ b/rfcs/003-nix-nickel.md @@ -31,8 +31,8 @@ flakes and modules. ### Constraints -Because Nix is a distinct project from Nickel which has existed for quite -some time now, we need operate under the following constraints: +Because Nix is a distinct project from Nickel which has existed for quite some +time now, we need operate under the following constraints: - _Do not require unreasonable changes to Nix itself_. While it's probably illusory to avoid any change in Nix at all, we must strive to keep them @@ -130,47 +130,95 @@ justify. ### Interaction -As underlined in [Interaction with Nixpkgs](#interaction-with-nixpkgs), an -FFI-like interaction would require a transparent bidirectional communication -between Nix and Nickel. This is made very hard by the nature of both languages, -which are lazy and functional, meaning that a Nix function called from Nickel -may incur arbitrary calls back to Nickel (and vice-versa) to apply a function or -to evaluate a thunk. Not only does this looks complex, but maintaining reasonable -efficiency seems to be a challenge too. +As underlined in [Interaction with Nixpkgs](#interaction-with-nixpkgs), an FFI +mechanism would require a bidirectional communication between Nix and Nickel. +This is made complicated by the nature of both languages, which are lazy and +functional, meaning that a Nix function called from Nickel may incur arbitrary +calls back to Nickel (and vice-versa) to apply functions or to evaluate thunk. +Not only does this seem complex, but also non trivial to be made efficient. Another possibility is to have everything evaluate in only one world, either Nix -and Nickel. Practically, we could either compile the Nix code to Nickel, or the -Nickel code to Nix, and run everything there. Compiling Nickel to Nix and run -everything on the Nix side would have the advantage of leveraging all the Nix -primitives and machinery freely. However, this solution simply nullifies a lot -of the value proposition of Nickel. Compiling Nickel to Nix is not trivial, and -would loose the native support for contracts (and thus good error reporting), -overriding, and so on, going back to potential degraded performance and bad -error messages. - -On the other hand, compiling Nix to Nickel appears simpler on paper. Nickel is -close to being a superset of Nix (the only non-trivial missing features being -the `with` construct and string contexts). This would preserve the Nickel -advances, and we could leverage directly any non-builtin Nix function, including -all the Nixpkgs library functions. - -The possible drawbacks of this approach: - - Performance - - On the fly, or not - - dynamic imports and typechecking - -We will also need to reimplement Nix builtins in Nickel (the compat layer). This -is an interesting milestone, because even without the compiler, this will allow -to write derivation in Pure Nickel. +or Nickel. Practically, we could compile the Nix code to Nickel or vice-versa, +and run everything on one side only. + +Compiling Nickel to Nix and run everything on the Nix side would have the +advantage of leveraging all the Nix primitives and machinery freely. However, +this solution simply nullifies a lot of the value proposition of Nickel. The +added value of contracts is mainly due to its careful design as a primitive +feature of the language, and while it could be simulated to some extent in Nix, +the error message and performances just wouldn't in par. Similarly, the merge +system being native is in part motivated by the perspective of better +performance and error message. This is hard to maintain if we just compile those +features away back to plain Nix code. + +On the other hand, compiling Nix to Nickel looks simpler at first sight. Nickel +is close to being a superset of Nix (the only non-trivial missing features being +the `with` construct and string contexts, but the latter doesn't even really +change the semantics). This would preserve the Nickel advances, and we could +leverage directly any non-builtin Nix function, including all the Nixpkgs +library functions. + +The potential drawbacks of this approach: + +- Performance: Nickel is just not in par with Nix at this point, so the + performance of Nixpkgs compiled to Nickel might be prohibitively bad at first. + On a positive note, this would provide a great benchmark suite to guide future + performance improvements. +- Some builtin may include effects, which are not trivial to combine with the + execution model (reversible thunks). That being said, those effects are in + fact idempotent and commutative (creating path in the store, creating + derivation, etc.), which should be fine. +- Compilation could be done on the fly (shouldn't be too long), or have a + Nixpkgs snapshot pre-compiled to Nickel, or a mix of both. + +This solutions require to reimplement Nix builtins in Nickel (a compatibility +layer). This is an interesting milestone in itself, because even without a +Nix-to-Nickel compiler, the compatibility layer would already make writing +derivations in pure Nickel possible. + +Note: what about dynamic imports? ### String Context -- Overloaded string constructors. - * Have a specific string delimiter `nix%"foo %{pkgs.hello}"%` - * Have a corresponding contract or builtin to enforce Nix strings everywhere. - * Use those in Nickel derivations. That means people will have errors with - standard strings. +Ideally, we would like to have a behavior similar to Nix string contexts +automatically track dependencies in a transparent way (you don't have to think +about it in Nix, using strings in a natural way). + +There is a tension between endowing Nickel with specific features for the Nix +use-case and keeping it a general and versatile language for configuration. + +One solution is to support string contexts in Nickel exactly as in Nix. But that +would do feel _ad hoc_. Other use-cases would benefit from a similar but more +flexible mechanism for automatic dependency tracking. For example, in Terraform, +interpolated expressions can refer to values that are only known after certain +resources have been deployed. The Terraform evaluator thus uses a similar +mechanism job to elaborate a deployment plan from string metadata. Using Nickel +for Terraform may need to replicate this mechanism in some way. + +We propose to adopt a more generic mechanism for strings with context, which +behavior can be parametrized. Strings become (conceptually) pairs `(s, ctxt)` +where `ctxt` is an arbitrary data structure with additional structure: + +```nickel +{ + combine: Ctxt -> Ctxt -> Ctxt, + pure: Str -> Ctxt, + pure_exp: Expr -> Ctxt, +} +``` -The question is how to make this process not Nix-specific but more general. Look -at g-expr in Guix (a bit of the same idea in the end?). Should the extension be -specified in Nickel itself, or as an interpreter plugin? +The above functions could either be provided by user code or directly as an +interpreter plug-in. + +- Existing standard strings would be equivalent to having a trivial context + `null` and the obvious corresponding trivial structure. +- Custom contexts would be introduced by specific string delimiters, such as + `nix%" %{pkgs.hello}/bin/hello"%` +- Different kind of strings are incompatible, to avoid accidentally forgetting + or losing contexts. Also, we don't know a priori how to convert one context to + another. +- There would be a contract to distinguish the different kind of + strings in order enforce the usage of e.g. Nix style contexts for writing + Nickel for Nix. We want to avoid users loosing context unknowingly. + +Alternative: something like `g-exp`. No magic, no extension, but less ergonomic. From c91c5d379efb8a7eb551c283f3d76e18829065d7 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Fri, 29 Apr 2022 17:53:48 +0200 Subject: [PATCH 07/20] Add section on hypothethical Nix-nickel + next todos --- rfcs/003-nix-nickel.md | 124 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 115 insertions(+), 9 deletions(-) diff --git a/rfcs/003-nix-nickel.md b/rfcs/003-nix-nickel.md index 39619034f0..eb57001977 100644 --- a/rfcs/003-nix-nickel.md +++ b/rfcs/003-nix-nickel.md @@ -61,9 +61,9 @@ time now, we need operate under the following constraints: Nix expressions are used in related but different ways, each coming with varying goals and constraints: -- Nixpkgs: derivations and packages in the original style. -- Flakes: new format for decentralized and composable packages. -- NixOS modules: NixOS system configuration. +- **Nixpkgs**: derivations and packages in the original style. +- **Flakes**: new format for decentralized and composable packages. +- **NixOS modules**: NixOS system configuration. In the long term, we aims at handling all those cases, but the scope of such an undertaking appears very large for a single RFC. We decide to focus on the first @@ -84,10 +84,6 @@ Thus, tackling derivations first makes sense. From there, Nixpkgs is probably the thinnest layer over derivations of the three, and already open a lot of possibilities, so we choose to include it as well. -## Nix-Nickel fantasised - -Examples of using Nickel for Nix, in practice. What we want. - ## Challenges ### Interaction with Nixpkgs @@ -123,11 +119,114 @@ variables usage inside string interpolation. Nix can then automatically determine the dependencies of a derivation. Abandoning the automatic dependency management offered by string contexts in -Nickel sounds like a degradation of developers' quality of life that is hard to -justify. +Nickel is an unacceptable degradation of the quality of life of Nix users. ## Proposal +### What could Nix-Nickel look like + +Let's imagine what would in practice our ideal way of writing packages in +Nickel. The main inspiration for this section is Eelco Dolstra's [reflection on +the Nix language of the future][nix-lang]. + +#### Package as data + +Despite Nix being branded as a _functional_ package manager, a perhaps +surprising conclusion of Eelco's document is that writing packages as actual +functions is retrospectively of questionable value. Functions need to be applied +(and thus arguments be produced) before we can access any data (which hurts +discoverability), their inputs are hard to override, etc. Overall, functions are +opaque _computations_ (or _codata_), which makes them hard to inspect and to +patch. The reflection above pushes to switch to a model where packages are +rather _data_. Of course, computations still take place -- this is after all the +whole point of having a configuration language -- but the right representation +for a package may be better separate and expose pure data and represent +computations as data dependencies. + +Concretely, a derivation would be better represented simply as a recursive +records: + +```nickel +builders.derivation = { + + # Interface + + name + | doc "Name of the derivation, used in the Nix store path." + | Str, + + version + | doc "Version of the derivation, used in the Nix store path." + | Str, + | default = "", + + builder + | doc "Command to be executed to build the derivation." + | Path, + + args + | doc "Arguments passed to the builder." + | Array Str + | default = [], + + outputs + | doc "Symbolic names of the outputs of this derivation." + | Array Str + | default = ["out"], + + env + | doc "Structured values passed to the builder." + | {_: Str} + # inherit doesn't exist in Nickel, but let's pretend, for conciseness + = {inherit outputs}, + + # Implementation + + drv + | doc "The resulting store derivation." + = + (env & { + name = "%{name}-%{version}"; + inherit builder args; + }) + |> builtin.derivation +} + +# ... other definitions building on this one + +# actual package +pkgs.hello = builders.unix_package & { + name = "hello", + version = "1.12", + description = "A program that produces a familiar, friendly greeting", + license = licenses.gpl, + + enable_gui + | doc "Enable GTK+ support." + | Bool + | default false, + + src = builders.fetchurl & { url = ..., sha256 = ... }, + + buildInputs = if enable_gui then [ gtk ] else [], +} +``` + +Thanks to laziness, `nix` could extract fields like `name` or `version` directly +without having to provide inputs or to evaluate `drv`. Those examples and ideas +are taken directly from [Eelco's report][nix-lang]. + + + +#### Cohabitation with Nixpkgs + +How would that model be able to co-exist with the current Nixpkgs architecture? +In particular, could we override a package from Nixpkgs and use it in one of our +derivation? + + + ### Interaction As underlined in [Interaction with Nixpkgs](#interaction-with-nixpkgs), an FFI @@ -222,3 +321,10 @@ interpreter plug-in. Nickel for Nix. We want to avoid users loosing context unknowingly. Alternative: something like `g-exp`. No magic, no extension, but less ergonomic. + + + +[nix-lang]: https://gist.github.com/edolstra/29ce9d8ea399b703a7023073b0dbc00d From ea4fd6ba8b6ef15bb6fa3c20d78bf2bd0af21100 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 2 May 2022 11:41:50 +0200 Subject: [PATCH 08/20] Update rfcs/003-nix-nickel.md Co-authored-by: Arnaud Spiwack --- rfcs/003-nix-nickel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/003-nix-nickel.md b/rfcs/003-nix-nickel.md index eb57001977..a6d1efa382 100644 --- a/rfcs/003-nix-nickel.md +++ b/rfcs/003-nix-nickel.md @@ -132,7 +132,7 @@ the Nix language of the future][nix-lang]. #### Package as data Despite Nix being branded as a _functional_ package manager, a perhaps -surprising conclusion of Eelco's document is that writing packages as actual +surprising conclusion of Eelco Dolstra's document is that writing packages as actual functions is retrospectively of questionable value. Functions need to be applied (and thus arguments be produced) before we can access any data (which hurts discoverability), their inputs are hard to override, etc. Overall, functions are From 6567fdb06eb6811b59f054f967b93557c659570b Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 2 May 2022 11:41:56 +0200 Subject: [PATCH 09/20] Update rfcs/003-nix-nickel.md Co-authored-by: Arnaud Spiwack --- rfcs/003-nix-nickel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/003-nix-nickel.md b/rfcs/003-nix-nickel.md index a6d1efa382..3f289d444a 100644 --- a/rfcs/003-nix-nickel.md +++ b/rfcs/003-nix-nickel.md @@ -136,7 +136,7 @@ surprising conclusion of Eelco Dolstra's document is that writing packages as ac functions is retrospectively of questionable value. Functions need to be applied (and thus arguments be produced) before we can access any data (which hurts discoverability), their inputs are hard to override, etc. Overall, functions are -opaque _computations_ (or _codata_), which makes them hard to inspect and to +opaque _computations_, which makes them hard to inspect and to patch. The reflection above pushes to switch to a model where packages are rather _data_. Of course, computations still take place -- this is after all the whole point of having a configuration language -- but the right representation From b973994028771e344813fcc7a2b350f666f9f68a Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 2 May 2022 11:57:29 +0200 Subject: [PATCH 10/20] Update rfcs/003-nix-nickel.md --- rfcs/003-nix-nickel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/003-nix-nickel.md b/rfcs/003-nix-nickel.md index 3f289d444a..9065198560 100644 --- a/rfcs/003-nix-nickel.md +++ b/rfcs/003-nix-nickel.md @@ -88,7 +88,7 @@ possibilities, so we choose to include it as well. ### Interaction with Nixpkgs -There a bunch of actions that require leveraging Nixpkgs: +There is a bunch of scenarios that require leveraging Nixpkgs: - **PKG**: Using a package from Nixpkgs, for example as a dependency - **LIB**: Use one of the myriad of helpers from Nixpkgs: `mkShell`, From 75b5e1eb1793e945eb72ade082610f79ebeca551 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Tue, 3 May 2022 15:57:52 +0200 Subject: [PATCH 11/20] WIP --- rfcs/003-nix-nickel.md | 43 ++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/rfcs/003-nix-nickel.md b/rfcs/003-nix-nickel.md index 9065198560..8cb6f7ade0 100644 --- a/rfcs/003-nix-nickel.md +++ b/rfcs/003-nix-nickel.md @@ -1,6 +1,6 @@ --- feature: nix-nickel -start-date: 2022-01-12 +start-date: 2022-04-20 author: Yann Hamdaoui --- @@ -81,7 +81,7 @@ Something as innocent as using `pkgs.writeScript` in an hypothetical NixOS Nickel module already implies to know how to generate derivations. Thus, tackling derivations first makes sense. From there, Nixpkgs is probably -the thinnest layer over derivations of the three, and already open a lot of +the thinnest layer over derivations of the three, and already opens a lot of possibilities, so we choose to include it as well. ## Challenges @@ -144,7 +144,7 @@ for a package may be better separate and expose pure data and represent computations as data dependencies. Concretely, a derivation would be better represented simply as a recursive -records: +record: ```nickel builders.derivation = { @@ -219,15 +219,33 @@ are taken directly from [Eelco's report][nix-lang]. -#### Cohabitation with Nixpkgs +#### Aren't we re-invinting flakes? -How would that model be able to co-exist with the current Nixpkgs architecture? -In particular, could we override a package from Nixpkgs and use it in one of our -derivation? +[Flakes][nix-flakes] are a new feature of Nix that make it easy to write and distribute composable +packages, and in a more reproducible way. The recent version of Nix (>= 2.5) -- +and in particular the CLI -- are centered around the flakes model. - +The schema of flakes is following the approach described above: representing +packages as data rather than functions. A flake is a record (an attribute set in +Nix terminology) with directly accessible metadata (like `name`, `description`, +etc.), an `inputs` field describing what inputs are needed, and an `outputs` +field which is a function producing derivations. -### Interaction +Flakes can leverage Nixpkgs transparently as well, which has its own flakes +wrapper. + + + + +### Nickel and Nixpkgs + + + + + + As underlined in [Interaction with Nixpkgs](#interaction-with-nixpkgs), an FFI mechanism would require a bidirectional communication between Nix and Nickel. @@ -291,8 +309,8 @@ would do feel _ad hoc_. Other use-cases would benefit from a similar but more flexible mechanism for automatic dependency tracking. For example, in Terraform, interpolated expressions can refer to values that are only known after certain resources have been deployed. The Terraform evaluator thus uses a similar -mechanism job to elaborate a deployment plan from string metadata. Using Nickel -for Terraform may need to replicate this mechanism in some way. +mechanism to elaborate a deployment plan from string metadata. Using Nickel for +Terraform would need to replicate this mechanism in some way. We propose to adopt a more generic mechanism for strings with context, which behavior can be parametrized. Strings become (conceptually) pairs `(s, ctxt)` @@ -324,7 +342,8 @@ Alternative: something like `g-exp`. No magic, no extension, but less ergonomic. [nix-lang]: https://gist.github.com/edolstra/29ce9d8ea399b703a7023073b0dbc00d +[nix-flakes]: https://nixos.wiki/wiki/Flakes From d2504d98ec1837835a4783c49b15e8c969e6146a Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Tue, 3 May 2022 17:18:15 +0200 Subject: [PATCH 12/20] WIP --- rfcs/003-nix-nickel.md | 93 +++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 38 deletions(-) diff --git a/rfcs/003-nix-nickel.md b/rfcs/003-nix-nickel.md index 8cb6f7ade0..bcb83a0ee1 100644 --- a/rfcs/003-nix-nickel.md +++ b/rfcs/003-nix-nickel.md @@ -216,36 +216,40 @@ Thanks to laziness, `nix` could extract fields like `name` or `version` directly without having to provide inputs or to evaluate `drv`. Those examples and ideas are taken directly from [Eelco's report][nix-lang]. +We wall call this approach the **PADM** (**P**ackage **A**s **D**ata **M**odel) +thereafter. + -#### Aren't we re-invinting flakes? - -[Flakes][nix-flakes] are a new feature of Nix that make it easy to write and distribute composable -packages, and in a more reproducible way. The recent version of Nix (>= 2.5) -- -and in particular the CLI -- are centered around the flakes model. +#### Aren't we re-inventing flakes? -The schema of flakes is following the approach described above: representing -packages as data rather than functions. A flake is a record (an attribute set in -Nix terminology) with directly accessible metadata (like `name`, `description`, -etc.), an `inputs` field describing what inputs are needed, and an `outputs` -field which is a function producing derivations. + -Flakes can leverage Nixpkgs transparently as well, which has its own flakes -wrapper. +[Flakes][nix-flakes] is a new feature of Nix that make it easy to write and +distribute composable packages, and in a more reproducible way. The recent +version of Nix (>= 2.5) -- and in particular the CLI -- are centered around the +flakes model. - - +The schema of flakes looks closer to the approach described above than Nixpkgs: +representing packages as data rather than functions. A flake is a record (an +attribute set in Nix terminology) with directly accessible metadata (like +`name`, `description`, etc.), an `inputs` field describing what inputs are +needed, and an `outputs` field which is a function producing derivations. -### Nickel and Nixpkgs +Flakes can leverage packages from Nixpkgs easily as well, which has its own +flakes wrapper. However, flakes still rely on Nixpkgs-based `mkDerivation` and +the like to build derivation, and thus on `override` and its variants to +override packages. While some of the metadata like the name and description are +top-level and directly accessible without having to evaluate a function, this is +not the case of a lot of other metadata that are still buried in the +derivation, like `version`, unlike the PADM. - - - +It remains to see how the PADM would interact with flakes and all the built-in +support flakes have in Nix today. - +### Nickel and Nixpkgs As underlined in [Interaction with Nixpkgs](#interaction-with-nixpkgs), an FFI mechanism would require a bidirectional communication between Nix and Nickel. @@ -268,36 +272,47 @@ system being native is in part motivated by the perspective of better performance and error message. This is hard to maintain if we just compile those features away back to plain Nix code. -On the other hand, compiling Nix to Nickel looks simpler at first sight. Nickel -is close to being a superset of Nix (the only non-trivial missing features being -the `with` construct and string contexts, but the latter doesn't even really -change the semantics). This would preserve the Nickel advances, and we could -leverage directly any non-builtin Nix function, including all the Nixpkgs -library functions. +On the other hand, compiling Nix to Nickel looks simpler. Nickel is close to +being a superset of Nix (the only non-trivial missing features being the `with` +construct and string contexts, but the latter doesn't even really change the +semantics). This would preserve the Nickel advances, and we could leverage +directly any non-builtin Nix function, including all the Nixpkgs library +functions. The potential drawbacks of this approach: - Performance: Nickel is just not in par with Nix at this point, so the performance of Nixpkgs compiled to Nickel might be prohibitively bad at first. - On a positive note, this would provide a great benchmark suite to guide future - performance improvements. + On a positive note, this would provide a great benchmark suite and motivation + to guide future performance improvements. - Some builtin may include effects, which are not trivial to combine with the - execution model (reversible thunks). That being said, those effects are in - fact idempotent and commutative (creating path in the store, creating + execution model (revertible thunks). That being said, those effects are in + practice idempotent and commutative (creating paths in the store, creating derivation, etc.), which should be fine. -- Compilation could be done on the fly (shouldn't be too long), or have a - Nixpkgs snapshot pre-compiled to Nickel, or a mix of both. -This solutions require to reimplement Nix builtins in Nickel (a compatibility +Compilation could be done on the fly (shouldn't be too long), or have a Nixpkgs +snapshot pre-compiled to Nickel, or a mix of both. + +This solution requires to reimplement Nix builtins in Nickel (a compatibility layer). This is an interesting milestone in itself, because even without a Nix-to-Nickel compiler, the compatibility layer would already make writing derivations in pure Nickel possible. -Note: what about dynamic imports? + -### String Context +#### Using Nixpkgs in the PADM -Ideally, we would like to have a behavior similar to Nix string contexts +Using a package from Nixpkgs in the PADM with a Nix-to-Nickel compiler would be +quite straightforward, as we could evaluate anything to a derivation whenever +needed. One would need to use Nixpkgs functions and idioms to, say, override an +attribute, but that's pretty hard to avoid, as a systematic translation from the +Nixpkgs model to the PADM doesn't seem trivial at first sight. + + + +### Supporting string contexts + +Ideally, we would like to have a behavior similar to Nix string contexts to automatically track dependencies in a transparent way (you don't have to think about it in Nix, using strings in a natural way). @@ -336,7 +351,7 @@ interpreter plug-in. another. - There would be a contract to distinguish the different kind of strings in order enforce the usage of e.g. Nix style contexts for writing - Nickel for Nix. We want to avoid users loosing context unknowingly. + Nickel for Nix. We want to avoid users loosing or missing context unknowingly. Alternative: something like `g-exp`. No magic, no extension, but less ergonomic. @@ -345,5 +360,7 @@ effects, including actual deployment (and not just build free effects AST), that may subsume the string contexts usage as well as other things like Terraform interpolation --> + + [nix-lang]: https://gist.github.com/edolstra/29ce9d8ea399b703a7023073b0dbc00d [nix-flakes]: https://nixos.wiki/wiki/Flakes From 7ca908df5220be122e2d11e279609137bb00f205 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Tue, 3 May 2022 23:12:54 +0200 Subject: [PATCH 13/20] WIP --- rfcs/003-nix-nickel.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rfcs/003-nix-nickel.md b/rfcs/003-nix-nickel.md index bcb83a0ee1..9b2e56779d 100644 --- a/rfcs/003-nix-nickel.md +++ b/rfcs/003-nix-nickel.md @@ -312,6 +312,8 @@ Nixpkgs model to the PADM doesn't seem trivial at first sight. ### Supporting string contexts +#### Nix-style + Ideally, we would like to have a behavior similar to Nix string contexts to automatically track dependencies in a transparent way (you don't have to think about it in Nix, using strings in a natural way). @@ -355,6 +357,9 @@ interpreter plug-in. Alternative: something like `g-exp`. No magic, no extension, but less ergonomic. +#### Effects + +Another possible route is to use [effects][nickel-effects]. [nix-lang]: https://gist.github.com/edolstra/29ce9d8ea399b703a7023073b0dbc00d [nix-flakes]: https://nixos.wiki/wiki/Flakes +[nickel-effects]: https://github.com/tweag/nickel/issues/85 From 5321c79621af525adb12ff38c378fb0adfa73970 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Wed, 4 May 2022 09:29:37 +0200 Subject: [PATCH 14/20] PADM -> PARM (easier to pronounce) --- rfcs/003-nix-nickel.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/rfcs/003-nix-nickel.md b/rfcs/003-nix-nickel.md index 9b2e56779d..bbf6b8a23e 100644 --- a/rfcs/003-nix-nickel.md +++ b/rfcs/003-nix-nickel.md @@ -216,7 +216,7 @@ Thanks to laziness, `nix` could extract fields like `name` or `version` directly without having to provide inputs or to evaluate `drv`. Those examples and ideas are taken directly from [Eelco's report][nix-lang]. -We wall call this approach the **PADM** (**P**ackage **A**s **D**ata **M**odel) +We wall call this approach the **PARM** (**P**ackage **A**s **R**records **M**odel) thereafter. -#### Using Nixpkgs in the PADM +#### Using Nixpkgs in the PARM -Using a package from Nixpkgs in the PADM with a Nix-to-Nickel compiler would be +Using a package from Nixpkgs in the PARM with a Nix-to-Nickel compiler would be quite straightforward, as we could evaluate anything to a derivation whenever needed. One would need to use Nixpkgs functions and idioms to, say, override an attribute, but that's pretty hard to avoid, as a systematic translation from the -Nixpkgs model to the PADM doesn't seem trivial at first sight. +Nixpkgs model to the PARM doesn't seem trivial at first sight. @@ -355,11 +355,14 @@ interpreter plug-in. strings in order enforce the usage of e.g. Nix style contexts for writing Nickel for Nix. We want to avoid users loosing or missing context unknowingly. +#### G-exps + Alternative: something like `g-exp`. No magic, no extension, but less ergonomic. #### Effects Another possible route is to use [effects][nickel-effects]. + +#### Specifying dependencies + + + +How to specify the dependencies, like gtk? Are they part of the huge fixpoint +that will be Nickelpkgs? We want to avoid dynamic scoping/open records as it +opens cans of worms. But specifying interfaces by hand in each packages sounds +laborious. + +The fixpoint could also recursively appear in a sub-field `pkgs` that could be +protected by the one top-level contract: + +```nickel +let rec PkgsList = { .... } in +let rec Nickelpkgs = { + +} & .. & { + buildInputs = [ pkgs.gtk ] + pkgs | PkgsList, +} .. +& { pkgs | PkgsList = Nickelpkgs } +``` #### Aren't we re-inventing flakes? @@ -298,7 +318,8 @@ layer). This is an interesting milestone in itself, because even without a Nix-to-Nickel compiler, the compatibility layer would already make writing derivations in pure Nickel possible. - +- TODO: what about dynamic imports? +- TODO: what about using a Nickel pkgs from Nix? #### Using Nixpkgs in the PARM From 4d08cede262dc3ac7f2774fc17545d35a9ca1270 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Wed, 4 May 2022 19:17:19 +0200 Subject: [PATCH 16/20] WIP --- rfcs/003-nix-nickel.md | 208 +++++++++++++++++++++++++++-------------- 1 file changed, 136 insertions(+), 72 deletions(-) diff --git a/rfcs/003-nix-nickel.md b/rfcs/003-nix-nickel.md index fb50cff155..a5b7f4604a 100644 --- a/rfcs/003-nix-nickel.md +++ b/rfcs/003-nix-nickel.md @@ -132,19 +132,18 @@ the Nix language of the future][nix-lang]. #### Package as data Despite Nix being branded as a _functional_ package manager, a perhaps -surprising conclusion of Eelco Dolstra's document is that writing packages as actual -functions is retrospectively of questionable value. Functions need to be applied -(and thus arguments be produced) before we can access any data (which hurts -discoverability), their inputs are hard to override, etc. Overall, functions are -opaque _computations_, which makes them hard to inspect and to +surprising conclusion of Eelco Dolstra's document is that writing packages as +actual functions is retrospectively of questionable value. Functions need to be +applied (and thus arguments be produced) before we can access any data (which +hurts discoverability), their inputs are hard to override, etc. Overall, +functions are opaque _computations_, which makes them hard to inspect and to patch. The reflection above pushes to switch to a model where packages are rather _data_. Of course, computations still take place -- this is after all the whole point of having a configuration language -- but the right representation -for a package may be better separate and expose pure data and represent +for a package may be better separated, exposing pure data and representing computations as data dependencies. -Concretely, a derivation would be better represented simply as a recursive -record: +Concretely, a derivation would be represented simply as a recursive record: ```nickel builders.derivation = { @@ -213,61 +212,127 @@ pkgs.hello = builders.unix_package & { ``` Thanks to laziness, `nix` could extract fields like `name` or `version` directly -without having to provide inputs or to evaluate `drv`. Those examples and ideas -are taken directly from [Eelco's report][nix-lang]. +without having to provide inputs or to evaluate `drv`. -We wall call this approach the **PARM** (**P**ackage **A**s **R**records **M**odel) -thereafter. +We wall call this approach the **PARM** (**P**ackage **A**s **R**records +**M**odel) thereafter. #### Specifying dependencies - +The `pkgs.hello` example unveils a difficulty about specifying dependencies. It +would work in a model where all the packages are defined in one place and +populate the same huge record that will give Nickelpkgs. But that would be +totally impracticable and anti-modular. -How to specify the dependencies, like gtk? Are they part of the huge fixpoint -that will be Nickelpkgs? We want to avoid dynamic scoping/open records as it -opens cans of worms. But specifying interfaces by hand in each packages sounds -laborious. +Writing the `pkgs.hello` in its own file in Nickel today would result in the +`gtk` occurrence raising an `unbound variable` error, and for a good reason: +`gtk` is used but seemingly defined nowhere. In practice, `gtk` is expected to +appear in the final recursive record that would Nickelpkgs. But from a language +point of view, allowing references to yet-to-be-defined identifiers, that is +having dynamic scoping, comes with its share of drawbacks: non-locality making +code harder to understand, inability to detect unbound variables statically +(incurring late error reporting), inability to properly typecheck such code, +etc. -The fixpoint could also recursively appear in a sub-field `pkgs` that could be -protected by the one top-level contract: +Let us review possible suctions: + +##### Explicit list + +One possibility is to specify explicitly all the +dependencies as record fields without definition: ```nickel -let rec PkgsList = { .... } in -let rec Nickelpkgs = { - -} & .. & { - buildInputs = [ pkgs.gtk ] - pkgs | PkgsList, -} .. -& { pkgs | PkgsList = Nickelpkgs } +# file hello.ncl +{ + # dependencies + gtk | NickelPackage, + + name = "hello", + version = "1.12", + description = "A program that produces a familiar, friendly greeting", + license = licenses.gpl, + + enable_gui + | doc "Enable GTK+ support." + | Bool + | default false, + + src = builders.fetchurl & { url = ..., sha256 = ... }, + + buildInputs = if enable_gui then [ gtk ] else [], + +} ``` +The contract may be optional as the infrastructure of an hypothetical Nickelpkg +would already apply the contract Package. + + + +##### Dynamic scoping + + + +A second solution would be to implement a form of dynamic scoping, but with a +special syntax, such that it doesn't impact normal uses of variables. Could +either have a special `self` or `super` keyword that refers to the final version +of the fixpoint, or a special syntax such as identifiers starting a specific +character: + +```nickel +{ + name = "hello", + version = "1.12", + description = "A program that produces a familiar, friendly greeting", + license = licenses.gpl, + + enable_gui + | doc "Enable GTK+ support." + | Bool + | default false, + + src = builders.fetchurl & { url = ..., sha256 = ... }, + + buildInputs = if enable_gui then [ super.gtk ] else [], + +} +``` + +**TODO**: other solutions? The pkg subfield, but seems like a lesser version of +the first proposal. Bazel-like includes? From experience, the semantics is +confusing, it's tied to the filesystem structure. And in the end it's still a +strange form of dynamic scoping: if we are to do it, let go for a proper version. + #### Aren't we re-inventing flakes? [Flakes][nix-flakes] is a new feature of Nix that make it easy to write and -distribute composable packages, and in a more reproducible way. The recent -version of Nix (>= 2.5) -- and in particular the CLI -- are centered around the +distribute composable packages, all in a more reproducible way. The recent +versions of Nix (>= 2.5) -- and in particular the CLI -- are centered around the flakes model. -The schema of flakes looks closer to the approach described above than Nixpkgs: -representing packages as data rather than functions. A flake is a record (an -attribute set in Nix terminology) with directly accessible metadata (like -`name`, `description`, etc.), an `inputs` field describing what inputs are -needed, and an `outputs` field which is a function producing derivations. +The schema of flakes looks closer to the PARM than to the Nixpkgs style. A flake +is a record (an attribute set in Nix terminology) with directly accessible +metadata (like `name`, `description`, etc.), an `inputs` field describing what +inputs are needed, and an `outputs` field which is a function producing +derivations. + +However, this only concerns a few top-level attributes. Other fields of the +derivation, such as `version`, `license`, etc. are still hidden under the output +function. Flakes are not a alternative to Nixpkgs, but a schema working on top +of it. Building derivations in a flake still relies on Nixpkgs mechanisms like +`mkDerivation`, and the overriding mechanisms are the same as well. Flakes +solves related but distinct issues (package composition and reproducibility). -Flakes can leverage packages from Nixpkgs easily as well, which has its own -flakes wrapper. However, flakes still rely on Nixpkgs-based `mkDerivation` and -the like to build derivation, and thus on `override` and its variants to -override packages. While some of the metadata like the name and description are -top-level and directly accessible without having to evaluate a function, this is -not the case of a lot of other metadata that are still buried in the -derivation, like `version`, unlike the PARM. +At first sight, it shouldn't be hard to mechanically wrap a package in the PARM +as a flake (in general, going from a "data" package to more "functional" package +is easy). On consideration, though, is that the schema of flakes could be reused +in the PARM, just for the sake of consistency. -It remains to see how the PARM would interact with flakes and all the built-in -support flakes have in Nix today. +**TODO**: clarify what this last sentence exactly means. ### Nickel and Nixpkgs @@ -275,8 +340,8 @@ As underlined in [Interaction with Nixpkgs](#interaction-with-nixpkgs), an FFI mechanism would require a bidirectional communication between Nix and Nickel. This is made complicated by the nature of both languages, which are lazy and functional, meaning that a Nix function called from Nickel may incur arbitrary -calls back to Nickel (and vice-versa) to apply functions or to evaluate thunk. -Not only does this seem complex, but also non trivial to be made efficient. +calls back to Nickel (and vice-versa) to apply functions or to evaluate thunks. +Not only does this seem complex, but also hard to be made efficient. Another possibility is to have everything evaluate in only one world, either Nix or Nickel. Practically, we could compile the Nix code to Nickel or vice-versa, @@ -287,36 +352,37 @@ advantage of leveraging all the Nix primitives and machinery freely. However, this solution simply nullifies a lot of the value proposition of Nickel. The added value of contracts is mainly due to its careful design as a primitive feature of the language, and while it could be simulated to some extent in Nix, -the error message and performances just wouldn't in par. Similarly, the merge +the error message and performances just couldn't be in par. Similarly, the merge system being native is in part motivated by the perspective of better -performance and error message. This is hard to maintain if we just compile those +performance and error message. This simply becomes moot if we compile those features away back to plain Nix code. -On the other hand, compiling Nix to Nickel looks simpler. Nickel is close to -being a superset of Nix (the only non-trivial missing features being the `with` -construct and string contexts, but the latter doesn't even really change the -semantics). This would preserve the Nickel advances, and we could leverage +On the other hand, compiling Nix to Nickel looks more appealing. Nickel is close +to being a superset of Nix (the only non-trivial missing features being the +`with` construct and string contexts, but the latter doesn't even really change +the semantics). This would preserve the Nickel advances, and we could leverage directly any non-builtin Nix function, including all the Nixpkgs library functions. The potential drawbacks of this approach: -- Performance: Nickel is just not in par with Nix at this point, so the - performance of Nixpkgs compiled to Nickel might be prohibitively bad at first. - On a positive note, this would provide a great benchmark suite and motivation - to guide future performance improvements. -- Some builtin may include effects, which are not trivial to combine with the +- Performance: Nickel is not expected to be in par with Nix at this point, so + the performance of Nixpkgs compiled to Nickel might be prohibitively bad at + first. On a positive note, this would provide a great benchmark suite and + motivation to guide future performance improvements. +- Some builtin may incur effects, which are not trivial to combine with the execution model (revertible thunks). That being said, those effects are in practice idempotent and commutative (creating paths in the store, creating derivation, etc.), which should be fine. -Compilation could be done on the fly (shouldn't be too long), or have a Nixpkgs -snapshot pre-compiled to Nickel, or a mix of both. +Compilation could be done on the fly, as the compilation process would be rather +straightforward, or have a Nixpkgs snapshot pre-compiled to Nickel, or a mix of +both. -This solution requires to reimplement Nix builtins in Nickel (a compatibility -layer). This is an interesting milestone in itself, because even without a -Nix-to-Nickel compiler, the compatibility layer would already make writing -derivations in pure Nickel possible. +This solution requires to reimplement Nix builtins in Nickel (the nickel-nix +compatibility layer) . This is an interesting milestone in itself, because even +without a Nix-to-Nickel compiler, the compatibility layer would already make +writing derivations in pure Nickel possible. - TODO: what about dynamic imports? - TODO: what about using a Nickel pkgs from Nix? @@ -324,10 +390,10 @@ derivations in pure Nickel possible. #### Using Nixpkgs in the PARM Using a package from Nixpkgs in the PARM with a Nix-to-Nickel compiler would be -quite straightforward, as we could evaluate anything to a derivation whenever -needed. One would need to use Nixpkgs functions and idioms to, say, override an -attribute, but that's pretty hard to avoid, as a systematic translation from the -Nixpkgs model to the PARM doesn't seem trivial at first sight. +straightforward, as we could evaluate anything to a derivation whenever needed. +One would need to use Nixpkgs functions and idioms to do e.g. overrides though, +but this seems pretty hard to avoid, as a systematic translation from the +Nixpkgs model to the PARM doesn't seem trivial, if even doable. @@ -354,12 +420,10 @@ We propose to adopt a more generic mechanism for strings with context, which behavior can be parametrized. Strings become (conceptually) pairs `(s, ctxt)` where `ctxt` is an arbitrary data structure with additional structure: -```nickel -{ - combine: Ctxt -> Ctxt -> Ctxt, - pure: Str -> Ctxt, - pure_exp: Expr -> Ctxt, -} +```Rust +combine: Ctxt -> Ctxt -> Ctxt, +pure: Str -> Ctxt, +pure_exp: Expr -> Ctxt, ``` The above functions could either be provided by user code or directly as an From 406ea25057e826c0931e3aa9ff5e7cbc3715536a Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Sun, 15 May 2022 09:59:49 +0200 Subject: [PATCH 17/20] Update rfcs/003-nix-nickel.md --- rfcs/003-nix-nickel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/003-nix-nickel.md b/rfcs/003-nix-nickel.md index a5b7f4604a..91ca1e7109 100644 --- a/rfcs/003-nix-nickel.md +++ b/rfcs/003-nix-nickel.md @@ -214,7 +214,7 @@ pkgs.hello = builders.unix_package & { Thanks to laziness, `nix` could extract fields like `name` or `version` directly without having to provide inputs or to evaluate `drv`. -We wall call this approach the **PARM** (**P**ackage **A**s **R**records +We call this approach the **PARM** (**P**ackage **A**s **R**records **M**odel) thereafter. #### Specifying dependencies From 537fc682d246bd4b9721d77480e51562ccebe8cd Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Sun, 15 May 2022 10:00:53 +0200 Subject: [PATCH 18/20] Update rfcs/003-nix-nickel.md --- rfcs/003-nix-nickel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/003-nix-nickel.md b/rfcs/003-nix-nickel.md index 91ca1e7109..5e7985ef8c 100644 --- a/rfcs/003-nix-nickel.md +++ b/rfcs/003-nix-nickel.md @@ -156,7 +156,7 @@ builders.derivation = { version | doc "Version of the derivation, used in the Nix store path." - | Str, + | Str | default = "", builder From 229dfe30e1cc9bc1887bf06c83358607fa99429a Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Mon, 19 Dec 2022 15:43:16 +0100 Subject: [PATCH 19/20] Update rfcs/003-nix-nickel.md Co-authored-by: Benoit de Chezelles --- rfcs/003-nix-nickel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/003-nix-nickel.md b/rfcs/003-nix-nickel.md index 5e7985ef8c..d4f32cefc6 100644 --- a/rfcs/003-nix-nickel.md +++ b/rfcs/003-nix-nickel.md @@ -214,7 +214,7 @@ pkgs.hello = builders.unix_package & { Thanks to laziness, `nix` could extract fields like `name` or `version` directly without having to provide inputs or to evaluate `drv`. -We call this approach the **PARM** (**P**ackage **A**s **R**records +We call this approach the **PARM** (**P**ackage **A**s **R**ecords **M**odel) thereafter. #### Specifying dependencies From 511c9ec9c7cc10c88cf265ae8c9d2c1c906cdebb Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Tue, 3 Jan 2023 20:14:48 +0100 Subject: [PATCH 20/20] Update on string context + typos --- rfcs/003-nix-nickel.md | 168 ++++++++++++++++++++++++++++++++--------- 1 file changed, 132 insertions(+), 36 deletions(-) diff --git a/rfcs/003-nix-nickel.md b/rfcs/003-nix-nickel.md index d4f32cefc6..15816effb1 100644 --- a/rfcs/003-nix-nickel.md +++ b/rfcs/003-nix-nickel.md @@ -32,7 +32,7 @@ flakes and modules. ### Constraints Because Nix is a distinct project from Nickel which has existed for quite some -time now, we need operate under the following constraints: +time now, we need to operate under the following constraints: - _Do not require unreasonable changes to Nix itself_. While it's probably illusory to avoid any change in Nix at all, we must strive to keep them @@ -51,8 +51,8 @@ time now, we need operate under the following constraints: - _Do not lock ourselves in the current Nixpkgs architecture_. Nixpkgs had made a number of design choices that are, in hindsight, not optimal (stdenv, describing packages as functions instead of data, etc.). While we have to - keep some form of backward-compatibility, we want to do so while avoiding to - tie ourselves to the current design and implementation. In other words, this + keep some form of backward-compatibility, we want to do so while avoiding + tying ourselves to the current design and implementation. In other words, this document must propose a solution that stay compatible with a future radical departure from e.g. the Nixpkgs architecture. @@ -65,7 +65,7 @@ goals and constraints: - **Flakes**: new format for decentralized and composable packages. - **NixOS modules**: NixOS system configuration. -In the long term, we aims at handling all those cases, but the scope of such an +In the long term, we aim at handling all those cases, but the scope of such an undertaking appears very large for a single RFC. We decide to focus on the first item: writing derivations and Nixpkgs-style packages. @@ -88,7 +88,7 @@ possibilities, so we choose to include it as well. ### Interaction with Nixpkgs -There is a bunch of scenarios that require leveraging Nixpkgs: +There are a bunch of scenarios that require leveraging Nixpkgs: - **PKG**: Using a package from Nixpkgs, for example as a dependency - **LIB**: Use one of the myriad of helpers from Nixpkgs: `mkShell`, @@ -101,10 +101,10 @@ JSON. **LIB** is more involved. Some of Nixpkgs helpers take either functions, but the sole laziness of expressions means that an interface (in the form of a FFI) -between Nix and Nickel would needs to handle going back and forth between the +between Nix and Nickel would need to handle going back and forth between the two languages transparently. -**OVD** is technically not very different than **LIB**, since it mostly amounts +**OVD** is technically not very different from **LIB**, since it mostly amounts to calling Nix functions like `override`, `overrideAttrs`, etc (for any override that doesn't operate directly on a derivation, which most are). That is, solving **LIB** would solve **OVD** as well. @@ -264,7 +264,7 @@ dependencies as record fields without definition: } ``` -The contract may be optional as the infrastructure of an hypothetical Nickelpkg +The contract may be optional as the infrastructure of a hypothetical Nickelpkg would already apply the contract Package. +Symbolic strings are applying the idea of G-expressions to strings: when using a +special string prefix (here `s`, but `s` is confusing and temporary), the string +is not evaluated but returned as an array of either string literals or +expressions instead. Then, the contract for `args` defined in the `nickel-nix` +library just applies `nix_string` under the hood. Deep down, this example is +strictly identical to the previous version, but symbolic strings offer a nice +and natural syntax to write trees of values as nested arrays. - +`nix_string` is just a Nickel library function implementing the logic of Nix +string context. Library writers can however define different interpreting +functions, such as a handler for Terraform's computed values, which makes +symbolic strings a generic mechanism as desired. [nix-lang]: https://gist.github.com/edolstra/29ce9d8ea399b703a7023073b0dbc00d [nix-flakes]: https://nixos.wiki/wiki/Flakes [nickel-effects]: https://github.com/tweag/nickel/issues/85 +[guix]: https://guix.gnu.org/ +[g-expr]: https://guix.gnu.org/manual/devel/en/html_node/G_002dExpressions.html +[guile-quasiquote]: https://www.gnu.org/software/guile/manual/html_node/Expression-Syntax.html#Expression-Syntax +[nickel-symbolic-strings]: https://github.com/tweag/nickel/issues/948 +[recursive-nix]: https://github.com/NixOS/nix/issues/13