diff --git a/pills/04-basics-of-language.xml b/pills/04-basics-of-language.xml index e2a19ca..95e33f9 100644 --- a/pills/04-basics-of-language.xml +++ b/pills/04-basics-of-language.xml @@ -4,5 +4,362 @@ version="5.0" xml:id="basics-of-language"> -basics of language + The basics of language + + + Welcome to the fourth Nix pill. In the previous + third pill we entered the Nix + environment. We installed software as user, managed the profile, switched + between generations, and queried the nix store. That's the very basics of + nix administration somehow. + + + + The + Nix language + is used to write derivations. The + nix-build + tool is used to build derivations. Even as a system administrator that + wants to customize the installation, it's necessary to master Nix. Using + Nix for your jobs means you get the features we saw in the previous pills + for free. + + + + The syntax is very uncommon thus looking at existing examples may lead to + thinking that there's a lot of magic behind. In reality, it's only about + writing utility functions for making things convenient. + + + + On the other hand, this same syntax is great for describing packages. + + + + Important: in Nix, everything is an + expression, there are no statements. This is common to many functional + languages. + + + + Important: values in Nix are + immutable. + + +
+ Value types + + + We've installed nix-repl in the previous pill. If you didn't, + nix-env -i nix-repl. The nix-repl syntax is slightly + different than nix syntax when it comes to assigning variables, but no + worries. I prefer playing with nix-repl with you before cluttering your + mind with more complex expressions. + + + + Launch nix-repl. First of all, nix supports basic arithmetic operations: + +, -, and *. The integer division can be done with builtins.div. + + + + + + Really, why doesn't nix have basic operations such as division? Because + it's not needed for creating packages. Nix is not a general purpose + language, it's a domain-specific language for writing packages. + + + + Just think that builtins.div is not being used in the whole of our + nixpkgs repository: it's useless. + + + + Other operators are ||, && and ! for booleans, and relational + operators such as !=, ==, <, >, <=, >=. In Nix, <, >, <= and + >= are not much used. There are also other operators we will see in the + course of this series. + + + + Nix has integer (not floating point), string, path, boolean and null + simple + types. Then there are lists, sets and functions. These types are enough + to build an operating system. + + + + Nix is strongly typed, but it's not statically typed. That is, you + cannot mix strings and integers, you must first do the conversion. + + + + Try to use / between two numbers: + + + + + + Nix parsed 2/3 as a relative path to the current directory. Paths are + parsed as long as there's a slash. Therefore to specify the current + directory, use ./. In addition, Nix also parses urls. + + + + Not all urls or paths can be parsed this way. If a syntax error occurs, + it's still possible to fallback to plain strings. Parsing urls and paths + are convenient for additional safety. + +
+ +
+ Identifier + + + Not much to say, except that dash (-) is allowed in identifiers. That's + convenient since many packages use dash in its name. In fact: + + + + + + As you can see, a-b is parsed as identifier, not as + operation between a and b. + +
+ +
+ Strings + + + It's important to understand the syntax for strings. When reading Nix + expressions at the beginning, you may find dollars ($) ambiguous in their + usage. Strings are enclosed by double quotes ("), or two single quotes + (''). + + + + + + In python you can use also single quotes for strings like 'foo', but not + in Nix. + + + + It's possible to + interpolate + whole Nix expressions inside strings with ${...} and only with ${...}, + not $foo or {$foo} or anything else. + + + + + + Note: ignore the foo = "strval" assignment, it's nix-repl special syntax. + + + + As said previously, you cannot mix integers and strings. You explicitly + need conversion. We'll see this later: function calls are another story. + + + + Using the syntax with two single quotes, it's useful for writing double + quotes inside strings instead of escaping: + + + + + + Escaping ${...} within double quoted strings is done with the backslash. + Within two single quotes, it's done with '': + + + + + + No other magic about strings for now. + +
+ +
+ Lists + + + Lists are a sequence of expressions delimited by space (not comma): + + + + + + Lists, like anything else in Nix, are immutable. Adding or removing + elements from a list is possible, but will return a new list. + +
+ +
+ Sets + + + Sets are an association between a string key and a Nix expression. Keys + can only be strings. When writing sets you can also use identifiers as + keys. + + + + + + Note: here the string representation from nix is wrong, you can't write + { 123 = "num"; } because 123 is not an identifier. You need semicomma + (;) after every key-value assignment. + + + + For those reading Nix expressions from nixpkgs: do not confuse sets with + argument sets used in functions. + + + + To access elements in the set: + + + + + + Yes, you can use strings for non-identifiers to address keys in the set. + + + + You cannot refer inside a set to elements of the same set: + + + + + + To do so, use + recursive sets: + + + + + + This will be very convenient when defining packages. + +
+ +
+ If expression + + + Expressions, not statements. + + + + + + You can't have only the "then" branch, you must specify also the "else" + branch, because an expression must have a value in all cases. + +
+ +
+ Let expression + + + This kind of expression is used to define local variables to inner + expressions. + + + + + + The syntax is: first assign variables, then "in" expression. The overall + result will be the final expression after "in". + + + + + + Let's write two let expressions, one inside the other: + + + + + + With let you cannot assign twice to the same variable. You can however + shadow outer variables: + + + + + + You cannot refer to variables in a let expression outside of it: + + + + + + You can refer to variables in the let expression when assigning variables + like with recursive sets: + + + + + + So beware when you want to refer to a variable from the outer scope, but + it's being defined in the current let expression. Same applies to + recursive sets. + +
+ +
+ With expression + + + This kind of expression is something you hardly see in other languages. + Think of it like a more granular "using" of C++, or "from module import + *" from Python. You decide per-expression when to include symbols into + the scope. + + + + + + That's it, it takes a set and includes symbols in the scope of the inner + expression. Of course, only valid identifiers from the set keys will be + included. If a symbol exists in the outer scope and also in the "with" + scope, it will not be shadowed. + You can however still refer to the set: + + + +
+ +
+ Laziness + + + Nix evaluates expression only when needed. This is a great feature when + working with packages. + + + + + + Since "a" is not needed, there's no error about division by zero, because + the expression is not in need to be evaluated. That's why we can have all + the packages defined here, yet access to specific packages very fast. + +
+ +
+ Next pill + + + ...we will talk about functions and imports. In this pill I've tried to + avoid function calls as much as possible, otherwise the post would have + been too long. + +
diff --git a/pills/04/basics.txt b/pills/04/basics.txt new file mode 100644 index 0000000..42f21eb --- /dev/null +++ b/pills/04/basics.txt @@ -0,0 +1,4 @@ +nix-repl> 1+3 +4 +nix-repl> builtins.div 6 3 +2 diff --git a/pills/04/dash.txt b/pills/04/dash.txt new file mode 100644 index 0000000..c018aaa --- /dev/null +++ b/pills/04/dash.txt @@ -0,0 +1,4 @@ +nix-repl> a-b +error: undefined variable `a-b' at (string):1:1 +nix-repl> a - b +error: undefined variable `a' at (string):1:1 diff --git a/pills/04/double-quotes.txt b/pills/04/double-quotes.txt new file mode 100644 index 0000000..aed382b --- /dev/null +++ b/pills/04/double-quotes.txt @@ -0,0 +1,4 @@ +nix-repl> ''test " test'' +"test \" test" +nix-repl> ''${foo}'' +"strval" diff --git a/pills/04/escaping.txt b/pills/04/escaping.txt new file mode 100644 index 0000000..041a502 --- /dev/null +++ b/pills/04/escaping.txt @@ -0,0 +1,4 @@ +nix-repl> "\${foo}" +"${foo}" +nix-repl> ''test ''${foo} test'' +"test ${foo} test" diff --git a/pills/04/if.txt b/pills/04/if.txt new file mode 100644 index 0000000..b496bc1 --- /dev/null +++ b/pills/04/if.txt @@ -0,0 +1,4 @@ +nix-repl> a = 3 +nix-repl> b = 4 +nix-repl> if a > b then "yes" else "no" +"no" diff --git a/pills/04/interpolate.txt b/pills/04/interpolate.txt new file mode 100644 index 0000000..ef3b069 --- /dev/null +++ b/pills/04/interpolate.txt @@ -0,0 +1,7 @@ +nix-repl> foo = "strval" +nix-repl> "$foo" +"$foo" +nix-repl> "${foo}" +"strval" +nix-repl> "${2+3}" +error: cannot coerce an integer to a string, at (string):1:2 diff --git a/pills/04/lazy.txt b/pills/04/lazy.txt new file mode 100644 index 0000000..f659943 --- /dev/null +++ b/pills/04/lazy.txt @@ -0,0 +1,2 @@ +nix-repl> let a = builtins.div 4 0; b = 6; in b +6 diff --git a/pills/04/let-basic.txt b/pills/04/let-basic.txt new file mode 100644 index 0000000..1b5a42e --- /dev/null +++ b/pills/04/let-basic.txt @@ -0,0 +1,2 @@ +nix-repl> let a = "foo"; in a +"foo" diff --git a/pills/04/let-multiple-assign.txt b/pills/04/let-multiple-assign.txt new file mode 100644 index 0000000..dc88d30 --- /dev/null +++ b/pills/04/let-multiple-assign.txt @@ -0,0 +1,4 @@ +nix-repl> let a = 3; a = 8; in a +error: attribute `a' at (string):1:12 already defined at (string):1:5 +nix-repl> let a = 3; in let a = 8; in a +8 diff --git a/pills/04/let-multiple.txt b/pills/04/let-multiple.txt new file mode 100644 index 0000000..5d94010 --- /dev/null +++ b/pills/04/let-multiple.txt @@ -0,0 +1,2 @@ +nix-repl> let a = 3; b = 4; in a + b +7 diff --git a/pills/04/let-nested.txt b/pills/04/let-nested.txt new file mode 100644 index 0000000..8acd36f --- /dev/null +++ b/pills/04/let-nested.txt @@ -0,0 +1,2 @@ +nix-repl> let a = 3; in let b = 4; in a + b +7 diff --git a/pills/04/let-reference.txt b/pills/04/let-reference.txt new file mode 100644 index 0000000..f947d7a --- /dev/null +++ b/pills/04/let-reference.txt @@ -0,0 +1,2 @@ +nix-repl> let a = 4; b = a + 5; in b +9 diff --git a/pills/04/let-scope.txt b/pills/04/let-scope.txt new file mode 100644 index 0000000..ddadd6d --- /dev/null +++ b/pills/04/let-scope.txt @@ -0,0 +1,2 @@ +nix-repl> let a = (let b = 3; in b); in b +error: undefined variable `b' at (string):1:31 diff --git a/pills/04/lists.txt b/pills/04/lists.txt new file mode 100644 index 0000000..70f3daf --- /dev/null +++ b/pills/04/lists.txt @@ -0,0 +1,2 @@ +nix-repl> [ 2 "foo" true (2+3) ] +[ 2 "foo" true 5 ] diff --git a/pills/04/relative-path.txt b/pills/04/relative-path.txt new file mode 100644 index 0000000..e9bc749 --- /dev/null +++ b/pills/04/relative-path.txt @@ -0,0 +1,2 @@ +nix-repl> 2/3 +/home/nix/2/3 diff --git a/pills/04/set-access.txt b/pills/04/set-access.txt new file mode 100644 index 0000000..50dcb65 --- /dev/null +++ b/pills/04/set-access.txt @@ -0,0 +1,4 @@ +nix-repl> s.a-b +"baz" +nix-repl> s."123" +"num" diff --git a/pills/04/set-basics.txt b/pills/04/set-basics.txt new file mode 100644 index 0000000..062f758 --- /dev/null +++ b/pills/04/set-basics.txt @@ -0,0 +1,3 @@ +nix-repl> s = { foo = "bar"; a-b = "baz"; "123" = "num"; } +nix-repl> s +{ 123 = "num"; a-b = "baz"; foo = "bar"; } diff --git a/pills/04/set-failed.txt b/pills/04/set-failed.txt new file mode 100644 index 0000000..4503bf4 --- /dev/null +++ b/pills/04/set-failed.txt @@ -0,0 +1,2 @@ +nix-repl> { a = 3; b = a+4; } +error: undefined variable `a' at (string):1:10 diff --git a/pills/04/set-recursive.txt b/pills/04/set-recursive.txt new file mode 100644 index 0000000..6513039 --- /dev/null +++ b/pills/04/set-recursive.txt @@ -0,0 +1,2 @@ +nix-repl> rec { a= 3; b = a+4; } +{ a = 3; b = 7; } diff --git a/pills/04/strings-basic.txt b/pills/04/strings-basic.txt new file mode 100644 index 0000000..05e8b75 --- /dev/null +++ b/pills/04/strings-basic.txt @@ -0,0 +1,4 @@ +nix-repl> "foo" +"foo" +nix-repl> ''foo'' +"foo" diff --git a/pills/04/with-basic.txt b/pills/04/with-basic.txt new file mode 100644 index 0000000..3d0ac96 --- /dev/null +++ b/pills/04/with-basic.txt @@ -0,0 +1,5 @@ +nix-repl> longName = { a = 3; b = 4; } +nix-repl> longName.a + longName.b +7 +nix-repl> with longName; a + b +7 diff --git a/pills/04/with-scope.txt b/pills/04/with-scope.txt new file mode 100644 index 0000000..d90f07e --- /dev/null +++ b/pills/04/with-scope.txt @@ -0,0 +1,4 @@ +nix-repl> let a = 10; in with longName; a + b +14 +nix-repl> let a = 10; in with longName; longName.a + b +7