This commit is contained in:
Amy de Buitléir 2025-10-12 15:58:37 +01:00
parent 3d8f0e2b8b
commit d0be400f85
9 changed files with 109 additions and 33 deletions

View file

@ -33,10 +33,17 @@ who answered my many questions,
especially `cdepillabout`, `FedericoSchonborn`, `tejing` and `smkuehnhold`.
Any mistakes in this book are my own, however.
I would also like to thank Luca Bruno (aka Lethalman).
Although I have never interacted with them personally,
I learned a great deal from
their series of tutorials called https://nixos.org/guides/nix-pills/[Nix pills].
include::intro/main.adoc[leveloffset=+1]
include::nix-language/main.adoc[leveloffset=+1]
include::nixpkgs/main.adoc[leveloffset=+1]
include::hello-flake/main-generated.adoc[leveloffset=+1]
include::hello-flake-repo/main-generated.adoc[leveloffset=+1]
@ -53,4 +60,11 @@ include::new-flake/main.adoc[leveloffset=+1]
include::recipes/main.adoc[leveloffset=+1]
// TODO Acknowledge and provide links to nix pills, nix reference manual
// TODO Use curly quotation marks (smart quotes)
// TODO Replace all references to "later" with proper cross-references.
// TODO Write about lazy evaluation file:///home/amy/github/eolas/nix/nix-language.md#lazy-evaluation
// TODO Write about currying and partial function application
// TODO Cover assertions file:///home/amy/github/eolas/nix/nix-language.md#lazy-evaluation
// TODO Cover fixed point file:///home/amy/github/eolas/nix/nix-language.md#fixed-point
// TODO Cover any material in file:///home/amy/github/eolas/nix/nix.md that I haven't already covered
// TODO Cover built-in constants file:///home/amy/github/eolas/nix/nix-language.md#built-in-constants

View file

@ -81,7 +81,7 @@ flake dependencies, (e.g., version number, branch, revision, hash), so
that _anyone, anywhere, any time, can re-create the exact same
environment that the original developer used._
No more complaints of "`but it works on my machine!`". That is the
No more complaints of "but it works on my machine!". That is the
benefit of using flakes.
////

View file

@ -1,11 +1,17 @@
= A new flake from scratch
At last we are ready to create a flake from scratch!
The sections in this chapter are very similar;
No matter what programming languages you normally use,
I recommend that you start by reading the <<_bash>> section.
In it, I start with an extremely simple flake, and show how to improve and extend it.
The remaining sections in this chapter are very similar;
read the one for your language of choice.
If you're interested in a language that I haven't covered, feel free to suggest it by creating an
https://codeberg.org/mhwombat/nix-book/issues[issue].
include::bash-flake/main-generated.adoc[leveloffset=+1]
include::haskell-flake/main-generated.adoc[leveloffset=+1]
include::python-flake/main-generated.adoc[leveloffset=+1]

View file

@ -65,22 +65,20 @@ $ python hello.py
== Configuring setuptools
Next, create a Python script to build the package. We'll use Python's
Next, configure the package. We'll use Python's
setuptools, but you can use other build tools. For more information on
setuptools, see the
https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/[Python
Packaging User Guide], especially the section on
https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/#setup-args[setup
args].
https://setuptools.pypa.io/en/latest/index.html[Setuptools documentation], especially the section on
https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html[pyproject.toml].
////
$ curl https://codeberg.org/mhwombat/hello-flake-python/raw/branch/main/setup.py --silent --output setup.py
$ curl https://codeberg.org/mhwombat/hello-flake-python/raw/branch/main/pyproject.toml --silent --output pyproject.toml
////
[source,python,linenums]
.setup.py
[source,toml,linenums]
.pyproject.toml
....
$# cat setup.py
$# cat pyproject.toml
....
== Building the program manually (optional)
@ -140,11 +138,15 @@ Let's start with the development shell. It seems logical to write
something like the following.
....
devShells = rec {
devShells = forAllSupportedSystems (system:
let
pkgs = nixpkgsFor.${system};
pythonEnv = pkgs.python3.withPackages (ps: [ ps.build ]);
in {
default = pkgs.mkShell {
packages = [ (python.withPackages (ps: with ps; [ build ])) ];
packages = [ pythonEnv ];
};
};
});
....
Note that we need the parentheses to prevent `python.withPackages` and
@ -153,31 +155,30 @@ wanted to work with `virtualenv` and `pip` instead of `build`. We could
write something like the following.
....
devShells = rec {
devShells = forAllSupportedSystems (system:
let
pkgs = nixpkgsFor.${system};
pythonEnv = pkgs.python3.withPackages (ps: [ ps.virtualenv ps.pip ]);
in {
default = pkgs.mkShell {
packages = [
# Python plus helper tools
(python.withPackages (ps: with ps; [
virtualenv # Virtualenv
pip # The pip installer
]))
];
packages = [ pythonEnv ];
};
};
});
....
For the package builder, we can use the `buildPythonApplication`
function.
....
packages = rec {
hello = python.pkgs.buildPythonApplication {
packages = forAllSupportedSystems (system:
let pkgs = nixpkgsFor.${system}; in rec {
hello-flake-python = pkgs.python3Packages.buildPythonApplication {
name = "hello-flake-python";
buildInputs = with python.pkgs; [ pip ];
pyproject = true;
src = ./.;
build-system = with pkgs.python3Packages; [ setuptools ];
};
default = hello;
};
});
....
If you put all the pieces together, your `flake.nix` should look

View file

@ -19,6 +19,7 @@ the key names in the attribute set _pattern are separated by commas.
Here's an example of a function that has an attribute set as an input parameter.
[source]
.Example
....
nix-repl> greet = { first, last }: "Hello ${first} ${last}! May I call you ${first}?"
@ -36,6 +37,7 @@ using the syntax `_name_ ? _value_`.
This is illustrated below.
[source]
.Example
....
nix-repl> greet = { first, last ? "whatever-your-lastname-is", topic ? "Nix" }: "Hello ${first} ${last}! May I call you ${first}? Are you enjoying learning ${topic}?"
@ -52,6 +54,7 @@ A function can allow the caller to supply argument sets that contain "extra" val
This is done with the special parameter `...`.
[source]
.Example
....
nix-repl> formatName = { first, last, ... }: "${first} ${last}"
....
@ -60,6 +63,7 @@ One reason for doing this is to allow the caller to pass the same argument set t
even though each function may not need all of the values.
[source]
.Example
....
nix-repl> person = { first="Joe"; last="Bloggs"; address="123 Main Street"; }
@ -78,6 +82,7 @@ It can be convenient for a function to be able to reference the argument set as
This is done using an _@-pattern_.
[source]
.Example
....
nix-repl> formatPoint = p@{ x, y, ... }: builtins.toXML p
@ -88,6 +93,7 @@ nix-repl> formatPoint { x=5; y=3; z=2; }
Alternatively, the @-pattern can appear _after_ the argument set, as in the example below.
[source]
.Example
....
nix-repl> formatPoint = { x, y, ... } @ p: builtins.toXML p
....
@ -98,6 +104,7 @@ In the example below, the function `greet` passes its argument set, including th
to the function `confirmAddress`.
[source]
.Example
....
nix-repl> confirmAddress = { address, ... }: "Do you still live at ${address}?"

View file

@ -5,6 +5,7 @@
The `.` operator selects an attribute from a set.
[source]
.Example
....
nix-repl> animal = { name = { first = "Professor"; last = "Paws"; }; age = 10; species = "cat"; }
@ -20,6 +21,7 @@ nix-repl> animal . name . first
We can use the `?` operator to find out if a set has a particular attribute.
[source]
.Example
....
nix-repl> animal ? species
true
@ -28,13 +30,31 @@ nix-repl> animal ? bicycle
false
....
== Modification
== Union
We can use the `//` operator to modify an attribute set.
Recall that Nix values are immutable, so the result is a new value (the original is not modified).
We can use the `//` operator to combine two attribute sets.
Attributes in the right-hand set take preference.
[source]
.Example
....
nix-repl> a = { x = 7; y = "hello"; }
nix-repl> b = { y = "wombat"; z = 3; }
nix-repl> a // b
{
x = 7;
y = "wombat";
z = 3;
}
....
This is often used to "modify" one or more values in the set.
Recall that Nix values are immutable, so the result is a new value (the original is not actually modified).
[source]
.Example
....
nix-repl> animal // { species = "tiger"; }
{ age = 10; name = { ... }; species = "tiger"; }
@ -47,6 +67,7 @@ An ordinary attribute set cannot refer to its own elements.
To do this, you need a _recursive_ attribute set.
[source]
.Example
....
nix-repl> { x = 3; y = 4*x; }
error: undefined variable 'x'
@ -70,6 +91,7 @@ For more information on these and other built-in functions, see the Nix Manual
Get an alphabetical list of the keys.
[source]
.Example
....
nix-repl> builtins.attrNames animal
[ "age" "name" "species" ]
@ -78,6 +100,7 @@ nix-repl> builtins.attrNames animal
Get the values associated with each key, in alphabetical order by the key names.
[source]
.Example
....
nix-repl> builtins.attrValues animal
[ 10 "Professor Paws" "cat" ]
@ -86,6 +109,7 @@ nix-repl> builtins.attrValues animal
What value is associated with a key?
[source]
.Example
....
nix-repl> builtins.getAttr "age" animal
10
@ -94,6 +118,7 @@ nix-repl> builtins.getAttr "age" animal
Does the set have a value for a key?
[source]
.Example
....
nix-repl> builtins.hasAttr "name" animal
true
@ -105,6 +130,7 @@ false
Remove one or more keys and associated values from a set.
[source]
.Example
....
nix-repl> builtins.removeAttrs animal [ "age" "species" ]
{ name = "Professor Paws"; }
@ -113,7 +139,9 @@ nix-repl> builtins.removeAttrs animal [ "age" "species" ]
Display an attribute set, including nested sets.
[source]
.Example
....
nix-repl> builtins.toJSON animal
"{\"age\":10,\"name\":{\"first\":\"Professor\",\"last\":\"Paws\"},\"species\":\"cat\"}"
....

View file

@ -4,6 +4,7 @@ The usual boolean operators are available.
Recall that earlier we set `a = 7` and `b = 3`.
[source]
.Example
....
nix-repl> a == 7 # equality test
true
@ -37,6 +38,7 @@ One operator that might be unfamiliar to you is _logical implication_, which use
The expression `u -> v` is equivalent to `!u || v`.
[source]
.Example
....
nix-repl> u = false

View file

@ -28,4 +28,5 @@ Afterward, come back to this chapter and read it in more detail.
While writing this book, I anticipated that readers would want to skip around,
alternating between pure learning and learning-by-doing.
I've tried to structure the book to support that;
sometimes repeating information from earlier chapters that you might have skipped.
providing extensive cross-references to earlier and later sections,
and sometimes repeating information from earlier chapters that you might have skipped.

View file

@ -8,6 +8,7 @@ where the _expression_ typically involves the _parameter_.
Consider the following example.
[source]
.Example
....
nix-repl> x: x + 1
«lambda @ «string»:1:1»
@ -17,6 +18,9 @@ We created a function that adds `1` to its input.
However, it doesn't have a name, so we can't use it directly.
Anonymous functions do have their uses, as we shall see shortly.
// TODO Add a cross-reference to "shortly"
// TODO Show that you can pass a function to a function
Note that the message printed by the Nix REPL when we created the function uses the term _lambda_.
This derives from a branch of mathematics called _lambda calculus_.
Lambda calculus was the inspiration for most functional languages such as Nix.
@ -25,6 +29,7 @@ Functional programmers often call anonymous functions "lambdas".
The Nix REPL confirms that the expression `x: x + 1` defines a function.
[source]
.Example
....
nix-repl> :t x: x + 1
a function
@ -37,6 +42,7 @@ Recall from <<type-lambda>> that functions can be treated like any other data ty
In particular, we can assign it to a variable.
[source]
.Example
....
nix-repl> f = x: x + 1
@ -44,6 +50,8 @@ nix-repl> f
«lambda @ «string»:1:2»
....
// TODO Show that you can pass a function to a function
Procedural languages such as C or Java often use parenthesis to apply a function to a value, e.g. `f(5)`.
Nix, like lambda calculus and most functional languages, does not require parenthesis for function application.
This reduces visual clutter when chaining a series of functions.
@ -51,6 +59,7 @@ This reduces visual clutter when chaining a series of functions.
Now that our function has a name, we can use it.
[source]
.Example
....
nix-repl> f 5
6
@ -63,6 +72,7 @@ To define a calculation that requires more than one parameter,
we create functions that return functions!
[source]
.Example
....
nix-repl> add = a: (b: a+b)
....
@ -77,6 +87,7 @@ I used parentheses to emphasise the inner function, but they aren't necessary.
More commonly we would write the following.
[source]
.Example
....
nix-repl> add = a: b: a+b
....
@ -85,6 +96,7 @@ If we only supply one parameter to `add`, the result is a new function rather th
Invoking a function without supplying all of the expected parameters is called _partial application_.
[source]
.Example
....
nix-repl> add 3 # Returns a function that adds 3 to any input
«lambda @ «string»:1:6»
@ -93,6 +105,7 @@ nix-repl> add 3 # Returns a function that adds 3 to any input
Now let's apply `add 3` to the value `5`.
[source]
.Example
....
nix-repl> (add 3) 5
8
@ -101,6 +114,7 @@ nix-repl> (add 3) 5
In fact, the parentheses aren't needed.
[source]
.Example
....
nix-repl> add 3 5
8
@ -118,6 +132,7 @@ and returns a new function that takes a single parameter `b`, and returns the va
Let's apply `add` to the value `3`, and give the resulting new function a name, `g`.
[source]
.Example
....
nix-repl> g = add 3
....
@ -126,6 +141,7 @@ The function `g` takes a single parameter and adds `3` to it.
The Nix REPL confirms that `g` is indeed a function.
[source]
.Example
....
nix-repl> :t g
a function
@ -134,6 +150,7 @@ a function
Now we can apply `g` to a number to get a new number.
[source]
.Example
....
nix-repl> g 5
8