Read it as data with functions.
An attribute set uses braces and named values ending in semicolons. A list uses square brackets and spaces. Strings use double quotes. A let expression introduces local bindings; the expression after in is its result.
A function is written argument: expression. Applying it uses a space, so double 21 is a function call. Nix is lazily evaluated: values are generally computed when something needs them.
let
double = x: x * 2;
tools = [ "git" "hello" ];
in {
answer = double 21;
packages = tools;
enabled = true;
}
A configuration file is an expression.
Save the previous example as example.nix. The command below evaluates it strictly and prints JSON. It does not build a package.
A function such as { pkgs, ... }: { … } takes an attribute set and returns another value. The ellipsis allows additional arguments. You will see this shape in NixOS modules.
nix-instantiate --eval --strict --json example.nix
# {"answer":42,"enabled":true,"packages":["git","hello"]}
Nix evaluates expressions. Shell commands appear only inside strings that a builder or hook later runs.