DEV Community

John Winston
John Winston

Posted on • Originally published at hugosum.com

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.

# example.nix
let
   acceptAdditional = true;
in {
  foo = [ "a" "b" ] ++
    (if config.bar.enable then [ "c" "d" ] else [ ]); # [!code highlight]
}
Enter fullscreen mode Exit fullscreen mode

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" ]
Enter fullscreen mode Exit fullscreen mode

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.

# example.nix
let
   acceptAdditional = true;
in {
  foo = [ "a" "b" ] ++
    (if acceptAdditional then [ "c" "d" ] else [ ]); # [!code --]
    (lib.lists.optionals acceptAdditional [ "c" "d" ]); # [!code ++]
}
Enter fullscreen mode Exit fullscreen mode

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.

# example.nix
let
    acceptAdditional = true;
in {
  foo = {
    a = "d";
    b = "e";
  } // (if acceptAdditional then {
    one = "1";
    two = "2";
  } else
    { });
}
Enter fullscreen mode Exit fullscreen mode

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"; }
Enter fullscreen mode Exit fullscreen mode

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.

# example.nix
let
    acceptAdditional = true;
in {
  foo = {
    a = "d";
    b = "e";
  } // (if acceptAdditional then {
    one = "1";
    two = "2";
    a = "new value";
  } else
    { });
}
Enter fullscreen mode Exit fullscreen mode

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"; }
Enter fullscreen mode Exit fullscreen mode

To simplify our code and avoid writing if expression, we can use the helper function lib.attrsets.optionalAttrs.

# example.nix
let
    acceptAdditional = true;
in {
  foo = {
    a = "d";
    b = "e";
  } // (lib.attrsets.optionalAttrs acceptAdditional {
    one = "1";
    two = "2";
    a = "new value";
  });
}
Enter fullscreen mode Exit fullscreen mode

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.

# configuration.nix
{ config, pkgs, lib, inputs, ... }:

{
  boot.plymouth.enable = true;
  boot.kernelParams = []
    ++ (lib.lists.optionals config.boot.plymouth.enable [ "quiet" "splash" ]);
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)