Functions

Named, anonymous, higher-order, and recursive.

Named Functions

Functions are defined with fn: a name, a parameter list in brackets, and a body. The body carries an implicit do, so you write as many expressions as you like and the function returns the last one. No return keyword required.

[fn greet [name]
  [println "Hello, {name}!"]]

This defines a function called greet that takes one argument. Thanks to that implicit do, you can add more expressions before the println and they all run in order.

Anonymous Functions

Drop the name and you have a lambda. These are first-class values: bind them to variables, pass them to other functions, return them from one. You will reach for them constantly alongside map, filter, and their kin.

[let double [fn [x] [* x 2]]]
[println [double 5]]  ; 10

The syntax matches a named function, only without the name between fn and the parameter list. There is no separate "lambda" or "arrow function" form to learn. It is all fn.

Multi-Arity

Sometimes one name should answer to different numbers of arguments. A multi-arity definition lets it: each arity is wrapped in parentheses inside the same fn form, and the runtime dispatches to the right one by argument count.

[fn greet
  ([name] "Hello, {name}!")
  ([first last] "Hello, {first} {last}!")]

Call [greet "Ada"] for the single-argument version, [greet "Ada" "Lovelace"] for the two-argument one. This reads more clearly than optional parameters or overloading, because each arity owns an explicit parameter list and body.

Variadic Functions

Put & before the last parameter to gather any remaining arguments into a vector. This suits functions that accept an open-ended list of inputs, like a logging function that takes a severity level and then any number of message fragments.

[fn log [level & msgs]
  [println [str level ": "
    [join msgs " "]]]]

Here, level captures the first argument and msgs captures everything else as a vector. So [log "WARN" "disk" "almost" "full"] would print WARN: disk almost full.

Higher-Order Functions

A higher-order function takes or returns another function. This is where functional programming earns its keep: rather than write loops, you describe transformations. The standard library gives you the classics. map transforms each element, filter keeps the ones you want, and fold reduces a collection to a single value.

[map [fn [x] [* x x]] #[1 2 3 4]]
; #[1 4 9 16]

[filter [fn [x] [> x 2]] #[1 2 3 4]]
; #[3 4]

A for loop that builds a new list by transforming each element is really map. A loop that skips some elements is really filter. Once you learn to see these shapes, they change how you think about working with data.

Closures

Functions capture variables from their enclosing scope, so a function can carry values from the moment it was defined. The classic example is a function factory:

[fn make-adder [n]
  [fn [x] [+ x n]]]

[let add5 [make-adder 5]]
[println [add5 10]]  ; 15

make-adder returns a new function that closes over n. Each call to make-adder mints a fresh closure with its own captured value, so add5 adds 5 wherever and whenever you call it.

Pipe

Chain a few transformations and nested function calls turn inside out, the first step buried deepest. pipe sets them upright, letting you write transformations top to bottom. The result of each step is passed as the last argument to the next.

[pipe #[1 2 3 4 5]
  [filter [fn [x] [> x 2]]]
  [map [fn [x] [* x 10]]]
  [each println]]

Read it straight down: start with the vector, keep values greater than 2, multiply each by 10, then print each one. Now the nested version: [each println [map [fn [x] [* x 10]] [filter [fn [x] [> x 2]] #[1 2 3 4 5]]]]. The pipe reads like a recipe. The nesting reads like an onion.

Tip

pipe eliminates deeply nested calls and temporary variables.

Recursion

Recursion and pattern matching belong together, and pairing them keeps recursive functions remarkably clean. You write the base case and the recursive case as match arms, and the shape of the code mirrors the shape of the problem.

[fn factorial [n]
  [match n
    0 1
    _ [* n [factorial [- n 1]]]]]

The _ pattern matches anything, so this reads like its own definition: factorial of 0 is 1, and factorial of anything else is n times factorial of n minus 1. The Pattern Matching guide goes deeper.