Conditionally add values into list or map in Nix
I often find myself conditionally adding values into a list or map when writing configurations and derivations in Nix. In this post I want to show you how to conditionally add values into list or map in Nix.
Similar to many other functional languages, all values are immutable, and no statement exists in Nix. Instead of relying on method such as push()
that would mutate an object, you have to use composition to add values into a list or map.
Conditionally add values into a list in Nix
To conditionally add values into a list in Nix, we can use the list concatenation operator ++
together with a if
expression. Remember if
in Nix is an expression instead of statement, therefore it can return value. The following example shows you how to add c
and d
into the list of foo
, if acceptAdditional
is true
.
let
acceptAdditional = true;
in {
foo = [ "a" "b" ] ++
(if config.bar.enable then [ "c" "d" ] else [ ]);
}
If we evaluate the above file with nix repl
, we can see that the value of foo
evaluated to the combination of foo
and bar
.
❯ nix repl --file example.nix
Welcome to Nix 2.18.1. Type :? for help.
Loading installable ''...
Added 1 variables.
nix-repl> foo
[ "a" "b" "c" "d" ]
We can simplify our code with the helper function lib.lists.optionals
for the same effect, where an empty list will be returned, if the condition is not true.
let
acceptAdditional = true;
in {
foo = [ "a" "b" ] ++
(if acceptAdditional then [ "c" "d" ] else [ ]);
(lib.lists.optionals acceptAdditional [ "c" "d" ]);
}
Conditionally add values into a map in Nix
To conditionally add values into a map in Nix, we can use the Update operator //
. The attributes of the map in the right-hand side of the operator, will be merged into the map in the left-hand side.
let
acceptAdditional = true;
in {
foo = {
a = "d";
b = "e";
} // (if acceptAdditional then {
one = "1";
two = "2";
} else
{ });
}
By evaluating it, we can see that attribute one
and two
is now merged into foo
.
❯ nix repl --file example.nix
Welcome to Nix 2.18.1. Type :? for help.
Loading installable ''...
Added 1 variables.
nix-repl> foo
{ a = "d"; b = "e"; one = "1"; two = "2"; }
If any attribute on the right-hand side’s map is identical with the left-hand side’s map, it will overwrite its value. This is really useful for conditionally setting value of attributes in a map.
let
acceptAdditional = true;
in {
foo = {
a = "d";
b = "e";
} // (if acceptAdditional then {
one = "1";
two = "2";
a = "new value";
} else
{ });
}
As you can see, the value of a
has been overwritten into the value of the same attribute of the right-hand side map.
❯ nix repl --file example.nix
Welcome to Nix 2.18.1. Type :? for help.
Loading installable ''...
Added 1 variables.
nix-repl> foo
{ a = "new value"; b = "e"; one = "1"; two = "2"; }
To simplify our code and avoid writing if
expression, we can use the helper function lib.attrsets.optionalAttrs
.
let
acceptAdditional = true;
in {
foo = {
a = "d";
b = "e";
} // (lib.attrsets.optionalAttrs acceptAdditional {
one = "1";
two = "2";
a = "new value";
});
}
The limitation for \\
operator is that it cannot merge recursively. If you want a recursive merge, you can use the helper function lib.attrsets.recursiveUpdate
.
Practical use cases for adding values conditionally
plymouth
is an application that allows you to display a custom splash screen when you boot a Linux machine. To make plymouth
useful and avoid disrupting the splash screen with logs, we have to set quiet
and splash
at boot.kernelParams
.
Instead of setting it directly at boot.kernelParams
, and comment it out when we disable plymouth
, we can use the aforementioned technique, and add those parameters to boot.kernelParams
when config.boot.plymouth.enable
is true
.
{ config, pkgs, lib, inputs, ... }:
{
boot.plymouth.enable = true;
boot.kernelParams = []
++ (lib.lists.optionals config.boot.plymouth.enable [ "quiet" "splash" ]);
}