Functions with default arguments are also very useful; especially since `nix-build` will call them automatically. Those are "always `rec`" too, which (a) makes them convenient for intermediate values, and (b) provides a fine-grained way to override some functionality. I used this to great effect at a previous employer, for wrangling a bunch of inter-dependent Maven projects; but here's a made-up example:
{
# Main project directory. Override to build a different version.
src ? pkgs.lib.cleanSource ./.
# Take these files from src by default, but allow them to be overridden
, config ? "${src}/config.json"
, script ? "${src}/script.sh"
# A couple of dependencies
, jq ? pkgs.jq
, pythonEnv ? python3.withPackages choosePyPackages
, extraDeps ? [] # Not necessary, but might be useful for callers
# Python is tricky, since it bakes all of its libraries into one derivation.
# Exposing intermediate parts lets us override just the interpreter, or just
# the set of packages, or both.
, python3 ? pkgs.python3
, choosePyPackages ? (p: pythonDeps p ++ extraPythonDeps p)
, pythonDeps ? (p: [ p.numpy ])
, extraPythonDeps ? (p: []) # Again, not necessary but maybe useful
# Most of our dependencies will ultimately come from Nixpkgs, so we should pin
# a known-good revision. However, we should also allow that to be overridden;
# e.g. if we want to pass the same revision into a bunch of projects, for
# consistency.
, pkgs ? import ./pinned-nixpkgs.nix
}:
# Some arbitrary result
pkgs.writeShellApplication {
name = "foo";
runtimeInputs = [ jq pythonEnv ] ++ extraDeps;
runtimeEnv = { inherit config; };
text = builtins.readFile script;
}
> Since it uses attrsets, we can use their existing functionality, like `rec` and `inherit`; rather than duplicating it.
`let` supports `inherit`, and is always `rec`. Or is that your point, that it is needlessly duplicated functionality?