functions

single-argument functions, currying, patterns, and builtins.

nix functions take one argument.

f = x: x + 1
f 3   # 4

x: x + 1. part before the colon is the argument. part after is the body. no parentheses, no =>, no fun keyword.

currying

two arguments? a function that returns a function:

add = a: b: a + b
add 10 3   # 13

a: b: a + b is a: (b: a + b). calling add 10 does not error. it returns a new function:

addTen = add 10
addTen 3   # 13
addTen 7   # 17
add = a: b: a + b
a function that returns a function
add 10=b: 10 + b
returns a new function with a = 10
add 10 3=13
both arguments applied

nixpkgs uses partial application everywhere. callPackage works because of it.

calling functions

function application is a space. f x calls f with x. multiple arguments chain: f a b c is ((f a) b) c.

no parentheses unless the argument is a compound expression:

f = x: x * 2
f 3 + 1     # 7
f (3 + 1)   # 8

f 3 + 1 is (f 3) + 1. function application binds tighter than arithmetic.

pattern matching on attribute sets

in practice, most nix functions destructure an attribute set:

{ pname, version, src }:

stdenv.mkDerivation {
  inherit pname version src;
}

{ pname, version, src }: is a pattern. nix matches the argument and binds each name.

defaults

{ pname, version, src, debug ? false }:

caller omits debug, it defaults to false.

greet = { name, greeting ? "hey" }: "${greeting}, ${name}"
greet { name = "nix"; greeting = "hello"; }
name="nix"
greeting="hello"
="hello, nix"
greet { name = "nix"; }
name="nix"
greeting="hey"(default)
="hey, nix"

... for extra attributes

passing an attribute not in the pattern is an error:

f = { a, b }: a + b
f { a = 1; b = 2; c = 3; }
# error: function called with unexpected argument 'c'

... accepts and ignores extras:

f = { a, b, ... }: a + b
f { a = 1; b = 2; c = 3; }   # 3

@

sometimes you need both the destructured names and the whole set:

{ pname, version, ... } @ args:

stdenv.mkDerivation (args // {
  meta.description = "${pname} ${version}";
})

@ args binds the entire argument to args. destructure what you need, pass the rest through.

builtins

built-in functions living in the builtins set:

builtins.length [ 1 2 3 ]                    # 3
builtins.map (x: x * 2) [ 1 2 3 ]            # [ 2 4 6 ]
builtins.filter (x: x > 2) [ 1 2 3 4 5 ]     # [ 3 4 5 ]
builtins.attrNames { b = 2; a = 1; }          # [ "a" "b" ]
builtin what it does
map f list apply f to each element
filter f list keep elements where f returns true
attrNames set keys, sorted
attrValues set values, in key-sorted order
hasAttr name set whether the key exists
elem x list whether x is in the list
foldl' f init list strict left fold
import path evaluate a nix file, return its value
throw msg abort evaluation

import ./foo.nix evaluates the file and returns whatever it produces. function? you get a function. attribute set? you get a set. no module system at this level. files evaluate to values.

the standard nix file

{ lib, stdenv, fetchurl, openssl ? null }:

stdenv.mkDerivation { ... }

single-argument function. argument is an attribute set. lib, stdenv, fetchurl destructured. openssl defaults to null. callPackage in nixpkgs calls this function with the right arguments. packages never import their dependencies manually.

lazy evaluation is the last language concept, and the one that makes the whole ecosystem possible.