Functions in Clojure
Learn how to define and use functions in Clojure - parameters, multi-arity, variadic arguments, recursion, closures, and higher-order functions
Functions are the heart of Clojure. As a functional Lisp, Clojure treats functions as first-class values: you can pass them as arguments, return them from other functions, store them in data structures, and build new functions by composing existing ones. There are no “methods” attached to objects here—just functions that take values and return values.
Because Clojure favors immutability, functions are typically pure: given the same inputs, they always return the same output and produce no side effects. This makes code easier to reason about, test, and parallelize. In this tutorial you’ll learn how to define functions with defn, how Clojure handles multiple arities and variadic arguments, how recursion replaces traditional loops, and how closures and higher-order functions let you treat behavior as data.
If you’ve only seen Hello World so far, this is where Clojure’s functional character really begins to show. Let’s build up from a simple named function to closures and function composition.
Defining and Calling Functions
The defn macro defines a named function. It takes a name, an optional docstring, a vector of parameters in square brackets, and a body. The value of the last expression in the body is automatically returned—there is no return keyword.
Create a file named functions.clj:
| |
defncombinesdef(bind a name) andfn(create a function) in one step.[name]is the parameter vector—parameters are listed inside square brackets.- The body
(str "Hello, " name "!")is the return value; Clojure functions return their last expression implicitly.
Multi-Arity and Variadic Functions
A single Clojure function can accept different numbers of arguments. Each arity is written as its own (params body) clause. You can also accept an arbitrary number of arguments using &, which gathers the rest into a sequence.
Create a file named functions_arity.clj:
| |
- Multi-arity lets one function provide default behavior—
(welcome)delegates to(welcome "stranger"). & numberscaptures every remaining argument as a sequence.applycalls a function with the elements of a sequence as its arguments, so(apply + [1 2 3])is(+ 1 2 3).
Recursion Instead of Loops
Functional languages favor recursion over mutable loop counters. A function calls itself with a smaller problem until it reaches a base case. Here is the classic factorial.
Create a file named functions_recursion.clj:
| |
(dec n)subtracts one fromn;(<= n 1)is the base case that stops recursion.- Each call waits for the next, so
(factorial 5)expands to(* 5 (* 4 (* 3 (* 2 1)))). - For very deep recursion, Clojure offers
loop/recurfor constant-stack iteration, but plain recursion is clear and correct for small inputs like these.
Closures and Higher-Order Functions
Functions are values in Clojure. A function can return another function that “closes over” the variables in scope—this is a closure. Functions that take or return other functions are called higher-order functions, and they power Clojure’s data-processing style.
Create a file named functions_higher_order.clj:
| |
(fn [x] (+ x n))capturesnfrom the enclosing scope—add-10always adds 10.map,filter, andreduceare higher-order functions; they take a function as their first argument.#(* % %)is the anonymous-function reader shorthand, where%is the first argument.- The
->>threading macro reads top-to-bottom, passing each result as the last argument of the next call—a clean alternative to deeply nested parentheses.
Running with Docker
Run any of the example files with the official Clojure image. Replace the filename as needed.
| |
Expected Output
Running functions.clj:
Hello, Ada!
7
Running functions_arity.clj:
Welcome, stranger!
Welcome, Rich!
15
Running functions_recursion.clj:
120
3628800
Running functions_higher_order.clj:
15
(1 4 9 16)
(0 2 4 6 8)
15
20
Key Concepts
defndefines named functions with an optional docstring, a parameter vector in square brackets, and an implicit return of the last expression.- No
returnkeyword—a function evaluates to the value of its final form. - Multi-arity lets one function name handle different argument counts, often to provide defaults by delegating between arities.
- Variadic arguments use
& restto collect extra arguments into a sequence, frequently combined withapply. - Recursion replaces loops in idiomatic functional code; always provide a base case to terminate.
- Closures capture surrounding bindings, letting you generate specialized functions like
make-adder. - Higher-order functions (
map,filter,reduce) take functions as arguments and are the backbone of Clojure’s data transformation pipelines. - The
->>threading macro makes nested function calls readable by piping a value through each step.
Comments
Loading comments...
Leave a Comment