Chapter 2
- Understanding Nix Functions
- What are Nix Functions?
- Understanding Function Structure: The Role of the Colon
- Declaring Functions: Single and Simulated "Multiple" Arguments
- Nix Functions being "first class citizens"
- Conclusion
- Resources
Understanding Nix Functions

Functions are the building blocks of Nix, appearing everywhere in Nix expressions and configurations. Mastering them is essential for writing effective Nix code and understanding tools like NixOS and Home Manager. This chapter explores how Nix functions work, focusing on their single-argument nature, currying, partial application, and their role in modules.
What are Nix Functions?
A Nix Function is a rule that takes an input (called an argument) and produces an output based on that input. Unlike many programming languages, Nix functions are designed to take exactly one argument at a time. This unique approach, combined with a technique called currying, allows Nix to simulate multi-argument functions in a flexible and reusable way.
Builtins
✔️ Nix Builtin Functions (Click to Expand)
The Nix expression evaluator has a bunch of functions and constants built in:
-
toString e
: (Convert the expressione
to a string) -
import path
: (Load, parse and return the Nix expression in the filepath
) -
throw x
: (Throw an error messagex
. Usually stops evaluation) -
map f list
: (Apply the functionf
to each element in thelist
)
First I wanted to explain the structure of Nix Functions, and then we will talk about their "first-class" nature in Nix.
Understanding Function Structure: The Role of the Colon
The colon (:
) acts as a clear separator within a function definition:
-
Left of the Colon: This is the function's argument. It's a placeholder name for a value that will be provided when the function is called.
-
Right of the Colon: This is the function body. It's the expression that will be evaluated when the function is invoked.
Think of function arguments as naming values that aren't known in advance. These names are placeholders that get filled with specific values when the function is used.
Example:
greet = personName: "Hello, ${personName}!";
-
Here,
personName
is the argument (the placeholder). -
"Hello, ${personName}!"
, is the function body (which uses the placeholder to create the greeting).
When you call the function, (click to see Output):
greet "Anonymous"
"Hello, Anonymous!"
-
The value
"Anonymous"
is substituted for thepersonName
placeholder within the function body. -
This structure is the foundation of all Nix functions, whether simple or complex.
Declaring Functions: Single and Simulated "Multiple" Arguments
Single-Argument Functions: The Basics
The simplest form of a Nix function takes a single argument. In Nix, function
definitions like x: x + 1
or personName: "Hello, ${personName}!";
are
anonymous lambda functions. They exist as values until they are assigned
to a variable.
- Click to see Output:
# This is an anonymous lambda function value:
# x: x + 1
inc = x: x + 1; # here we assigned our lambda to a variable `inc`
inc 5
6
-
x
is the argument. -
x + 1
is the function body. -
This straightforward design makes single-argument functions easy to understand and use. But what if you need a function that seems to take multiple arguments? That's where currying comes in.
Simulating Multiple Arguments: Currying
To create functions that appear to take multiple arguments, Nix uses currying. This involves nesting single-argument functions, where each function takes one argument and returns another function that takes the next argument, and so on.
- Click to see Output:
# concat is equivalent to:
# concat = x: (y: x + y);
concat = x: y: x + y;
concat 6 6 # Evaluates to 12
12
Here, concat
is actually two nested functions
-
The first function takes
x
and returns another function. -
The second function takes
y
and performsx + y
Nix interprets the colons (:
) as separators for this chain of single-argument
functions.
Here's how it works step by step:
-
When you call
concat 6
, the outer function bindsx
to6
and returns a new function:y: 6 + y
. -
When you call that function with
6
(i.e.,concat 6 6
), it computes6 + 6
, resulting in12
.
This chaining is why Nix functions are so powerful—it allows you to build flexible, reusable functions.
A More Practical Example: Greetings:
Let's explore currying with a more relatable example in the nix repl
:
nix repl
nix-repl> greeting = prefix: name: "${prefix}, ${name}!";
nix-repl> greeting "Hello"
<<lambda @ <<string>>:1:10>> # partial application returns a lambda
nix-repl> greeting "Hello" "Alice"
"Hello, Alice!" # providing both arguments returns the expected result
This function is a chain of two single-argument functions:
-
The outer function takes
prefix
(e.g."Hello"
) and returns a function that expectsname
. -
The inner function takes
name
(e.g."Alice"
) and combines it withprefix
to produce the final string.
Thanks to lexical scope (where inner functions can access variables from
outer functions), the inner function "remembers" the prefix
value.
Why Currying Matters
-
You can partially apply arguments and reuse functions.
-
The "first-class" aspect of Nix Functions, explained further down.
-
It can help break down complex logic into smaller, manageable functions.
Key Insight: Every colon in a function definition separates a single argument from its function body, even if that body is another function definition.
Partial Application: Using Functions Incrementally
✔️ Partial Application (Click to Expand)
Because of currying, you can apply arguments to a Nix function one at a time. This is called partial application. When you provide only some of the expected arguments, you get a new function that "remembers" the provided arguments and waits for the rest.
Example:
Using our greeting
function again:
nix repl
nix-repl> greeting = prefix: name: "${prefix}, ${name}!";
nix-repl> helloGreeting = greeting "Hello";
nix-repl> helloGreeting "Alice"
"Hello, Alice"
helloGreeting
is now a new function. It has already received theprefix
argument ("Hello"
), when we provide the second argument we get"Hello, Alice!"
Benefits of Partial Application:
-
Creating Specialized Functions: You can create more specific functions from general ones by fixing some of their parameters.
-
Adapting to Higher-Order Functions: Many functions that operate on other functions (like
map
andfilter
) expect functions with a certain number of arguments. Partial application allows you to adapt existing functions to fit these requirements.
Nix Functions being "first class citizens"
In the context of Nix, the phrase "Nix treats functions as first-class citizens" means that functions in Nix are treated as values, just like numbers, strings, or lists. They can be manipulated, passed around, and used in the same flexible ways as other data types. This concept comes from functional programming and has specific implications in Nix.
What It Means in Nix
- Functions Can Be Assigned to Variables:
-
You can store a function in a variable, just like you would store a number or string.
-
Example:
greet = name: "Hello, ${name}!";
- Here, greet is a variable that holds a function.
- Functions Can Be Passed as Arguments:
-
You can pass a function to another function as an argument, allowing for higher-order functions (functions that operate on other functions).
-
Example:
applyTwice = f: x: f (f x);
inc = x: x + 1;
applyTwice inc 5 # Output: 7 (increments 5 twice: 5 → 6 → 7)
7
- Here, applyTwice takes a function
f
(in this case,inc
) and applies it tox
twice.
- Functions Can Be Returned from Functions:
-
Functions can produce other functions as their output, which is key to currying in Nix.
-
Example:
greeting = prefix: name: "${prefix}, ${name}!";
helloGreeting = greeting "Hello"; # Returns a function
helloGreeting "Alice" # Output: "Hello, Alice!"
"Hello, Alice!"
- The greeting function returns another function when partially applied with prefix.
- Functions Are Values in Expressions:
-
Functions can be used anywhere a value is expected, such as in attribute sets or lists.
-
Example:
myFuncs = {
add = x: y: x + y;
multiply = x: y: x * y;
};
myFuncs.add 3 4 # Output: 7
7
-
Here, functions are stored as values in an attribute set.
-
To try this in the
repl
just remove the semi-colon (;
)
Why This Matters in Nix:
-
This functional approach is fundamental to Nix's unique build system. In Nix, package builds (called derivations) are essentially functions. They take specific inputs (source code, dependencies, build scripts) and deterministically produce outputs (a built package).
- This design ensures atomicity: if a build does not succeed completely and perfectly, it produces no output at all. This prevents situations common in other package managers where partial updates or corrupted builds can leave your system in an inconsistent or broken state.
-
Many NixOS and Home Manager modules are functions, and their first-class status means they can be combined, reused, or passed to other parts of the configuration system.
-
Now that we understand the "first-class" nature of Nix Functions let's see how they fit into NixOS and Home Manager modules.
The Function Nature of NixOS and Home Manager Modules
It's crucial to understand that most NixOS and Home Manager modules are fundamentally functions.
- These module functions typically accept a single argument: an attribute set (remember this, it's important to understand).
Example:
A practical NixOS module example for Thunar with plugins:
# thunar.nix
{pkgs, ...}: {
programs = {
thunar = {
enable = true;
plugins = with pkgs.xfce; [
thunar-archive-plugin
thunar-volman
];
};
};
}
- To use this module I would need to import it into my
configuration.nix
or equivalent, shown here for completeness.
# configuration.nix
# ... snip ...
imports = [ ../nixos/thunar.nix ];
# ... snip ...
-
This is actually a pretty good example of
with
making it a bit harder to reason where the plugins are from. You might instinctively try to trace a path likeprograms.thunar.plugins.pkgs.xfce
because you sawpkgs.xfce
in thewith
statement. But that's now howwith
works. Thepkgs.xfce
path exists outside theplugins
list, defining the source of the items, not their nested structure within the list. -
To follow best practices you could write the above plugins section as:
plugins = [
pkgs.xfce.thunar-archive-plugin
pkgs.xfce.thunar-volman
];
-
Now it's clear that each plugin comes directly from
pkgs
and each will resolve to a derivation.- To be clear either way is fine, especially in such a small self contained
module. If it were in a single file
configuration.nix
it would be a bit more confusing to trace. Explicitness is your friend with Nix and maintaining reproducability.with
isn't always bad but should be avoided at the top of a file for example to bringnixpkgs
into scope, uselet
instead.
- To be clear either way is fine, especially in such a small self contained
module. If it were in a single file
-
The entire module definition is a function that takes one argument (an attribute set):
{ pkgs, ... }
. -
When this module is included in your configuration, the NixOS module system calls this function with a specific attribute set. This attribute set contains the available packages (
pkgs
), and other relevant information. The module then uses these values to define parts of your system.
Conclusion
Having explored the fundamental nature of functions in Nix, we can now see this concept applies to more complex areas like NixOS configuration. In the next chapter, NixOS Modules Explained. We will learn about NixOS Modules which are themselves functions most of the time.