diff --git a/source/book.adoc b/source/book.adoc index 2119b49..e1c52b4 100644 --- a/source/book.adoc +++ b/source/book.adoc @@ -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 diff --git a/source/hello-flake-repo/main.adoc0 b/source/hello-flake-repo/main.adoc0 index 8eee556..07888f8 100644 --- a/source/hello-flake-repo/main.adoc0 +++ b/source/hello-flake-repo/main.adoc0 @@ -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. //// diff --git a/source/new-flake/main.adoc b/source/new-flake/main.adoc index 312f325..5628bbc 100644 --- a/source/new-flake/main.adoc +++ b/source/new-flake/main.adoc @@ -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] diff --git a/source/new-flake/python-flake/main.adoc0 b/source/new-flake/python-flake/main.adoc0 index 70ee33f..a809d92 100644 --- a/source/new-flake/python-flake/main.adoc0 +++ b/source/new-flake/python-flake/main.adoc0 @@ -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 diff --git a/source/nix-language/argument-sets.adoc b/source/nix-language/argument-sets.adoc index 3c42b71..4dc235c 100644 --- a/source/nix-language/argument-sets.adoc +++ b/source/nix-language/argument-sets.adoc @@ -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}?" diff --git a/source/nix-language/attribute-sets.adoc b/source/nix-language/attribute-sets.adoc index 0d5ec79..849e726 100644 --- a/source/nix-language/attribute-sets.adoc +++ b/source/nix-language/attribute-sets.adoc @@ -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\"}" .... + diff --git a/source/nix-language/booleans.adoc b/source/nix-language/booleans.adoc index a0c404d..23f0bf6 100644 --- a/source/nix-language/booleans.adoc +++ b/source/nix-language/booleans.adoc @@ -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 diff --git a/source/nix-language/dont-read.adoc b/source/nix-language/dont-read.adoc index 9f84d68..05ce35f 100644 --- a/source/nix-language/dont-read.adoc +++ b/source/nix-language/dont-read.adoc @@ -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. diff --git a/source/nix-language/functions.adoc b/source/nix-language/functions.adoc index 5d14d9a..03c2523 100644 --- a/source/nix-language/functions.adoc +++ b/source/nix-language/functions.adoc @@ -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 <> 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