functions and imports
+ functions and imports
+
+
+
+
+ Welcome to the fifth Nix pill. In the previous fourth pill we touched the Nix language for a moment. We introduced basic types and values of the Nix language, and basic expressions such as if, with and let. I invite you to re-read about these expressions and play with them in the repl.
+
+
+
+ Functions help to build reusable components in a big repository like nixpkgs. The Nix manual has a great explanation of functions. Let's go: pill on one hand, Nix manual on the other hand.
+
+
+
+ I remind you how to enter the Nix environment: source ~/.nix-profile/etc/profile.d/nix.sh
+
+
+
+
+
+ Nameless and single parameter
+
+
+ Functions are anonymous (lambdas), and only have a single parameter. The syntax is extremely simple. Type the parameter name, then ":", then the body of the function.
+
+
+
+
+
+ So here we defined a function that takes a parameter x, and returns x*2. The problem is that we cannot use it in any way, because it's unnamed... joke!
+
+
+
+ We can store functions in variables.
+
+
+
+
+
+ As usual, please ignore the special syntax for assignments inside nix-repl. So, we defined a function x: x*2 that takes one parameter x, and returns x*2. This function is then assigned to the variable double. Finally we did our first function call: double 3.
+
+
+
+ Big note: it's not like many other programming languages where you write double(3). It really is double 3.
+
+
+
+ In summary: to call a function, name the variable, then space, then the argument. Nothing else to say, it's as easy as that.
+
+
+
+
+ More than one parameter
+
+
+ How do we create a function that accepts more than one parameter? For people not used to functional programming, this may take a while to grasp. Let's do it step by step.
+
+
+
+
+
+ We defined a function that takes the parameter a, the body returns another function. This other function takes a parameter b and returns a*b. Therefore, calling mul 3 returns this kind of function: b: 3*b. In turn, we call the returned function with 4, and get the expected result.
+
+
+
+ You don't have to use parenthesis at all, Nix has sane priorities when parsing the code:
+
+
+
+
+
+ Much more readable, you don't even notice that functions only receive one argument. Since the argument is separated by a space, to pass more complex expressions you need parenthesis. In other common languages you would write mul(6+7, 8+9).
+
+
+
+ Given that functions have only one parameter, it is straightforward to use partial application:
+
+
+
+
+
+ We stored the function returned by mul 3 into a variable foo, then reused it.
+
+
+
+
+ Arguments set
+
+
+ Now this is a very cool feature of Nix. It is possible to pattern match over a set in the parameter. We write an alternative version of mul = a: b: a*b first by using a set as argument, then using pattern matching.
+
+
+
+
+
+ In the first case we defined a function that accepts a single parameter. We then access attributes a and b from the given set. Note how the parenthesis-less syntax for function calls is very elegant in this case, instead of doing mul({ a=3; b=4; }) in other languages.
+
+
+
+ In the second case we defined an arguments set. It's like defining a set, except without values. We require that the passed set contains the keys a and b. Then we can use those a and b in the function body directly.
+
+
+
+
+
+ Only a set with exactly the attributes required by the function is accepted, nothing more, nothing less.
+
+
+
+
+ Default and variadic attributes
+
+
+ It is possible to specify default values of attributes in the arguments set:
+
+
+
+
+
+ Also you can allow passing more attributes (variadic) than the expected ones:
+
+
+
+
+
+ However, in the function body you cannot access the "c" attribute. The solution is to give a name to the given set with the @-pattern:
+
+
+
+
+
+ That's it, you give a name to the whole parameter with name@ before the set pattern.
+
+
+
+ Advantages of using argument sets:
+
+
+
+
+
+ Named unordered arguments: you don't have to remember the order of the arguments.
+
+
+
+
+ You can pass sets, that adds a whole new layer of flexibility and convenience.
+
+
+
+
+
+ Disadvantages:
+
+
+
+
+
+ Partial application does not work with argument sets. You have to specify the whole attribute set, not part of it.
+
+
+
+
+
+ You may find similarities with Python **kwargs.
+
+
+
+
+ Imports
+
+
+ The import function is built-in and provides a way to parse a .nix file. The natural approach is to define each component in a .nix file, then compose by importing these files.
+
+
+
+ Let's start with the bare metal.
+
+
+
+ a.nix:
+
+
+
+
+
+ b.nix:
+
+
+
+
+
+ mul.nix:
+
+
+
+
+
+
+
+ Yes it's really that straight. You import a file, and it gets parsed as expression. Note that the scope of the imported file does not inherit the scope of the importer.
+
+
+
+ test.nix:
+
+
+
+
+
+
+
+ So how do we pass information to the module? Use functions, like we did with mul.nix. A more complex example:
+
+
+
+ test.nix:
+
+
+
+
+
+
+ Explaining:
+
+
+
+
+
+ In test.nix we return a function. It accepts a set, with default attributes b, trueMsg and falseMsg.
+
+
+
+
+ builtins.trace is a built-in function that takes two arguments. The first is the message to display, the second is the value to return. It's usually used for debugging purposes.
+
+
+
+
+ Then we import test.nix, and call the function with that set.
+
+
+
+
+
+ So when is the message shown? Only when it's in need to be evaluated.
+
+
+
+
+ Next pill
+
+
+ ...we will finally write our first derivation.
+
+
+
diff --git a/pills/05/a-nix.txt b/pills/05/a-nix.txt
new file mode 100644
index 0000000..e440e5c
--- /dev/null
+++ b/pills/05/a-nix.txt
@@ -0,0 +1 @@
+3
\ No newline at end of file
diff --git a/pills/05/anon-function.txt b/pills/05/anon-function.txt
new file mode 100644
index 0000000..21cadb5
--- /dev/null
+++ b/pills/05/anon-function.txt
@@ -0,0 +1,2 @@
+nix-repl> x: x*2
+«lambda»
\ No newline at end of file
diff --git a/pills/05/argument-set-error.txt b/pills/05/argument-set-error.txt
new file mode 100644
index 0000000..a741df9
--- /dev/null
+++ b/pills/05/argument-set-error.txt
@@ -0,0 +1,4 @@
+nix-repl> mul { a = 3; b = 4; c = 6; }
+error: anonymous function at (string):1:2 called with unexpected argument `c', at (string):1:1
+nix-repl> mul { a = 3; }
+error: anonymous function at (string):1:2 called without required argument `b', at (string):1:1
\ No newline at end of file
diff --git a/pills/05/b-nix.txt b/pills/05/b-nix.txt
new file mode 100644
index 0000000..bf0d87a
--- /dev/null
+++ b/pills/05/b-nix.txt
@@ -0,0 +1 @@
+4
\ No newline at end of file
diff --git a/pills/05/default-values.txt b/pills/05/default-values.txt
new file mode 100644
index 0000000..6613ddd
--- /dev/null
+++ b/pills/05/default-values.txt
@@ -0,0 +1,5 @@
+nix-repl> mul = { a, b ? 2 }: a*b
+nix-repl> mul { a = 3; }
+6
+nix-repl> mul { a = 3; b = 4; }
+12
\ No newline at end of file
diff --git a/pills/05/import.txt b/pills/05/import.txt
new file mode 100644
index 0000000..1defa99
--- /dev/null
+++ b/pills/05/import.txt
@@ -0,0 +1,5 @@
+nix-repl> a = import ./a.nix
+nix-repl> b = import ./b.nix
+nix-repl> mul = import ./mul.nix
+nix-repl> mul a b
+12
\ No newline at end of file
diff --git a/pills/05/mul-nix.txt b/pills/05/mul-nix.txt
new file mode 100644
index 0000000..5c42a1b
--- /dev/null
+++ b/pills/05/mul-nix.txt
@@ -0,0 +1 @@
+a: b: a*b
\ No newline at end of file
diff --git a/pills/05/multi-argument-function.txt b/pills/05/multi-argument-function.txt
new file mode 100644
index 0000000..8bd584d
--- /dev/null
+++ b/pills/05/multi-argument-function.txt
@@ -0,0 +1,7 @@
+nix-repl> mul = a: (b: a*b)
+nix-repl> mul
+«lambda»
+nix-repl> mul 3
+«lambda»
+nix-repl> (mul 3) 4
+12
\ No newline at end of file
diff --git a/pills/05/named-function.txt b/pills/05/named-function.txt
new file mode 100644
index 0000000..3d61774
--- /dev/null
+++ b/pills/05/named-function.txt
@@ -0,0 +1,5 @@
+nix-repl> double = x: x*2
+nix-repl> double
+«lambda»
+nix-repl> double 3
+6
\ No newline at end of file
diff --git a/pills/05/named-set-argument.txt b/pills/05/named-set-argument.txt
new file mode 100644
index 0000000..1ed840b
--- /dev/null
+++ b/pills/05/named-set-argument.txt
@@ -0,0 +1,3 @@
+nix-repl> mul = s@{ a, b, ... }: a*b*s.c
+nix-repl> mul { a = 3; b = 4; c = 2; }
+24
\ No newline at end of file
diff --git a/pills/05/no-parenthesis.txt b/pills/05/no-parenthesis.txt
new file mode 100644
index 0000000..36d12ff
--- /dev/null
+++ b/pills/05/no-parenthesis.txt
@@ -0,0 +1,9 @@
+nix-repl> mul = a: b: a*b
+nix-repl> mul
+«lambda»
+nix-repl> mul 3
+«lambda»
+nix-repl> mul 3 4
+12
+nix-repl> mul (6+7) (8+9)
+221
\ No newline at end of file
diff --git a/pills/05/partial-application.txt b/pills/05/partial-application.txt
new file mode 100644
index 0000000..eb925b0
--- /dev/null
+++ b/pills/05/partial-application.txt
@@ -0,0 +1,5 @@
+nix-repl> foo = mul 3
+nix-repl> foo 4
+12
+nix-repl> foo 5
+15
\ No newline at end of file
diff --git a/pills/05/set-argument.txt b/pills/05/set-argument.txt
new file mode 100644
index 0000000..792b6f5
--- /dev/null
+++ b/pills/05/set-argument.txt
@@ -0,0 +1,6 @@
+nix-repl> mul = s: s.a*s.b
+nix-repl> mul { a = 3; b = 4; }
+12
+nix-repl> mul = { a, b }: a*b
+nix-repl> mul { a = 3; b = 4; }
+12
\ No newline at end of file
diff --git a/pills/05/test-import.txt b/pills/05/test-import.txt
new file mode 100644
index 0000000..02b4bd7
--- /dev/null
+++ b/pills/05/test-import.txt
@@ -0,0 +1,2 @@
+nix-repl> let x = 5; in import ./test.nix
+error: undefined variable `x' at /home/lethal/test.nix:1:1
\ No newline at end of file
diff --git a/pills/05/test-nix-2.txt b/pills/05/test-nix-2.txt
new file mode 100644
index 0000000..cd88a08
--- /dev/null
+++ b/pills/05/test-nix-2.txt
@@ -0,0 +1,7 @@
+{ a, b ? 3, trueMsg ? "yes", falseMsg ? "no" }:
+if a > b
+ then builtins.trace trueMsg true
+ else builtins.trace falseMsg false
+nix-repl> import ./test.nix { a = 5; trueMsg = "ok"; }
+trace: ok
+true
\ No newline at end of file
diff --git a/pills/05/test-nix.txt b/pills/05/test-nix.txt
new file mode 100644
index 0000000..c1b0730
--- /dev/null
+++ b/pills/05/test-nix.txt
@@ -0,0 +1 @@
+x
\ No newline at end of file
diff --git a/pills/05/variadic-arguments.txt b/pills/05/variadic-arguments.txt
new file mode 100644
index 0000000..05fdfec
--- /dev/null
+++ b/pills/05/variadic-arguments.txt
@@ -0,0 +1,2 @@
+nix-repl> mul = { a, b, ... }: a*b
+nix-repl> mul { a = 3; b = 4; c = 2; }
\ No newline at end of file