← Back to Course Index

Lesson 3: Functions & Composition

Master pure functions and reusable code patterns

Learning Objectives

Functions in Nix

Functions are pure—they always return the same output for the same input, with no side effects. This is Nix's superpower for reproducibility.

Function syntax: parameter: body or { param1, param2 }: body

Simple Functions

Single-Parameter Functions

add5 = x: x + 5; add5 3 # Evaluates to 8

Multiple Parameters

Nix functions take one parameter at a time. Multiple parameters are curried:

add = x: y: x + y; add 3 4 # Evaluates to 7 # Or use named parameters for clarity: add2 = { x, y }: x + y; add2 { x = 3; y = 4; } # Evaluates to 7

Default Parameters

# With defaults, using '?' greet = { name ? "friend", greeting ? "Hello" }: "${greeting}, ${name}!"; greet { } # "Hello, friend!" greet { name = "alice"; } # "Hello, alice!" greet { name = "bob"; greeting = "Hi"; } # "Hi, bob!"

Practical Function Patterns

Building a Package Definition

mkPackage = { name, version, dependencies ? [] }: { inherit name version dependencies; fullName = "${name}-${version}"; description = "Package: ${name}"; }; pkg = mkPackage { name = "my-app"; version = "1.0.0"; dependencies = [ "nodejs" "python3" ]; }; pkg.fullName # "my-app-1.0.0"
inherit keyword: inherit name is shorthand for name = name. Saves typing in attribute sets.

Function Composition

Combine functions to build complex logic:

double = x: x * 2; addTen = x: x + 10; compose = f: g: x: f (g x); # Compose two functions result = compose double addTen 5; # (5 + 10) * 2 = 30

Higher-Order Functions

Map (Transform Lists)

# Apply a function to every element map = f: list: builtins.map f list; names = [ "alice" "bob" "charlie" ]; greetings = map (name: "Hello, ${name}!") names; # [ "Hello, alice!" "Hello, bob!" "Hello, charlie!" ]

Filter (Select Elements)

numbers = [ 1 2 3 4 5 6 ]; evens = builtins.filter (n: n % 2 == 0) numbers; # [ 2 4 6 ]

Fold (Reduce to a Single Value)

numbers = [ 1 2 3 4 5 ]; sum = builtins.foldl' (acc: n: acc + n) 0 numbers; # 15

Using with and let for Clarity

let double = x: x * 2; triple = x: x * 3; numbers = [ 1 2 3 ]; in builtins.map (n: double n + triple n) numbers # [ 5 10 15 ] (each element: 2x + 3x = 5x)

Try It Yourself

Interactive Exercise

Open the Nix REPL:
nix repl
Define and call a simple function:
nix-repl> square = x: x * x nix-repl> square 5 25
Create a function with named parameters and defaults:
nix-repl> greet = { name ? "world" }: "Hello, ${name}!" nix-repl> greet {} "Hello, world!" nix-repl> greet { name = "alice"; } "Hello, alice!"
Map over a list:
nix-repl> numbers = [ 1 2 3 4 5 ] nix-repl> builtins.map (x: x * 2) numbers [ 2 4 6 8 10 ]
Chain operations:
nix-repl> data = [ 1 2 3 4 5 ] nix-repl> evens = builtins.filter (n: n % 2 == 0) data nix-repl> doubled = builtins.map (n: n * 2) evens nix-repl> doubled [ 4 8 ]

Challenge: Build a Function Library

Create reusable utility functions. Write these in the REPL:

let # Function to greet a person greetPerson = { name, title ? "Friend" }: "Hello, ${title} ${name}!"; # Function to check if number is even isEven = n: n % 2 == 0; # Filter and greet people = [ { name = "alice"; } { name = "bob"; title = "Dr"; } ]; in builtins.map (p: greetPerson p) people

Key Takeaways