mirror of
https://codeberg.org/mhwombat/nix-book.git
synced 2026-05-13 19:42:21 +08:00
expanded
This commit is contained in:
parent
3d8f0e2b8b
commit
d0be400f85
9 changed files with 109 additions and 33 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
////
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}?"
|
||||
|
||||
|
|
|
|||
|
|
@ -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\"}"
|
||||
....
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue