Skip to content

Combinator Syntax

Combinator syntax allows you to pass functions to import-tree to configure and immediately use it, reducing boilerplate and enabling fluent composition:

# Without combinators (verbose)
let
configured = import-tree.withLib lib;
leafs = configured.leafs;
in
leafs ./modules
# With combinators (terse)
import-tree (it: it.withLib lib) (it: it.leafs) ./modules

When you call import-tree with a function (a zero-argument function), it receives the current import-tree object and can call any methods on it:

import-tree (self: self.withLib lib)

The function is applied, and the result replaces the state. You can chain multiple functions:

import-tree
(it: it.withLib lib) # first: add lib
(it: it.addPath ./modules) # second: add path
(it: it.filter (lib.hasSuffix "mod.nix")) # third: filter
./src # finally: evaluate

This is purely syntactic sugar - it’s equivalent to piping through the operations in order.

Get a filtered file list in one expression:

import-tree
(it: it.withLib lib)
(it: it.filter (lib.hasSuffix "mod.nix"))
(it: it.leafs)
./modules
import-tree
(it: it.addScoped { helpers = my-utils; })
./modules

Combinators work with custom API methods:

let
custom = import-tree.addAPI {
nixFilesOnly = self: self.match ".*\\.nix$";
};
in
custom
(it: it.nixFilesOnly)
(it: it.leafs)
./src

Combinators shine for:

  • One-off file discovery in scripts
  • Building configurations inline without intermediate variables
  • Chaining multiple small transformations

For complex, reusable patterns, consider instead building domain-specific methods with .addAPI.

Contribute Community Sponsor