Functions & Definitions
Functions are the heart of Lean programming. Learn to define them with explicit types, type inference, named arguments, and default values.
Basic Function Definitions
Use the def keyword to define functions. The syntax is clean and expressive:
1-- A function with explicit types2def add (x : Nat) (y : Nat) : Nat := x + y34-- Using the function5#eval add 3 5 -- 867-- A function that takes a String8def greet (name : String) : String := "Hello, " ++ name ++ "!"910#eval greet "Lean" -- "Hello, Lean!"The pattern is: def name (params) : ReturnType := body. Each parameter has its own parentheses and type annotation.
Type Inference
Lean's type inference is powerful. You can often omit types and let the compiler figure them out:
1-- Type inferred for simple values2def x := 42 -- Inferred: Nat3def name := "Alice" -- Inferred: String45-- Type inferred for function parameters6def double x := x + x -- Inferred: Nat → Nat78-- But explicit types are clearer for functions9def double' (x : Nat) : Nat := x + xCommon Pitfalls
Beginners often run into small syntax gotchas. Keep these in mind as you practice.
- Each parameter usually gets its own parentheses unless you group same-typed parameters.
- Lean is expression-based—every branch must return the same type.
- Use
:=for definitions and=for equations.
Multiple Parameters
Functions can take multiple parameters. Each parameter is in its own parentheses, or you can group parameters of the same type:
1-- Each parameter separate2def add1 (x : Nat) (y : Nat) : Nat := x + y34-- Grouped parameters of same type 5def add2 (x y : Nat) : Nat := x + y67-- Mixed types8def describe (name : String) (age : Nat) : String :=9 s!"{name} is {age} years old"1011#eval describe "Alice" 30 -- "Alice is 30 years old"Named Arguments
You can call functions with named arguments for clarity:
1def greet (name : String) (formal : Bool) : String :=2 if formal then s!"Good day, {name}." else s!"Hey {name}!"34-- Positional arguments5#eval greet "Alice" true -- "Good day, Alice."67-- Named arguments (order doesn't matter)8#eval greet (name := "Bob") (formal := false) -- "Hey Bob!"9#eval greet (formal := true) (name := "Carol") -- "Good day, Carol."Default Values
Parameters can have default values, making them optional:
1def greet (name : String) (formal : Bool := false) : String :=2 if formal then s!"Good day, {name}." else s!"Hey {name}!"34-- Using default5#eval greet "Alice" -- "Hey Alice!" (formal defaults to false)67-- Override default8#eval greet "Bob" true -- "Good day, Bob."910-- Named argument to override11#eval greet "Carol" (formal := true) -- "Good day, Carol.":= value are optional. This pattern is common for configuration options and flags.Anonymous Functions (Lambdas)
Create inline functions with fun (or the Unicode λ):
1-- Lambda syntax2#eval (fun x => x + 1) 5 -- 63#eval (fun x y => x * y) 3 4 -- 1245-- With type annotations6#eval (fun (x : Nat) => x * 2) 5 -- 1078-- Unicode lambda (optional but pretty)9#eval (λ x => x + 1) 5 -- 61011-- Lambdas are often used with higher-order functions12#eval [1, 2, 3].map (fun x => x * 2) -- [2, 4, 6]Implicit Arguments
Some arguments can be inferred from context. These are marked with curly braces:
1-- The type α is implicit - Lean infers it2def identity {α : Type} (x : α) : α := x34-- Lean infers α = Nat from the argument5#eval identity 42 -- 426#eval identity "hello" -- "hello" (α = String)78-- You can provide implicit args explicitly with @9#eval @identity Nat 42 -- 42Deep Dive: Why Implicit Arguments?
Implicit arguments reduce verbosity. Without them, you'd have to writeidentity Nat 42 every time, even though Lean can clearly see 42 is a Nat.
The @ prefix makes all arguments explicit, which is useful when type inference fails or you want to be specific.
Instance-Implicit Arguments
Square brackets [...] denote instance-implicit arguments. Lean automatically finds type class instances:
1-- [BEq α] means "α must have equality defined"2def contains [BEq α] (xs : List α) (target : α) : Bool :=3 xs.any (· == target)45-- Lean finds the BEq instance automatically6#eval contains [1, 2, 3] 2 -- true7#eval contains ["a", "b"] "c" -- false89-- Multiple instance arguments10def sortAndDedupe [Ord α] [BEq α] (xs : List α) : List α :=11 xs.mergeSort |>.dedup1213-- Named instance argument (rare but useful)14def customCompare [inst : Ord α] (x y : α) : Ordering :=15 inst.compare x y[BEq α], Lean finds the appropriate equality implementation at compile time.Autobound Implicit Arguments
When you use an undeclared lowercase identifier as a type, Lean automatically makes it an implicit argument:
1-- These are equivalent:2def first1 (xs : List α) : Option α := xs.head?3def first2 {α : Type} (xs : List α) : Option α := xs.head?45-- α is automatically bound as {α : Type}67-- Works with multiple type variables8def zip (xs : List α) (ys : List β) : List (α × β) := 9 xs.zip ys10-- Both α and β are autobound1112-- Disable with set_option13set_option autoImplicit false in14def explicit {α : Type} (x : α) : α := xFunction Types
Functions have types expressed with arrows. Understanding these helps you read library documentation:
1-- Single argument function2#check (fun x => x + 1 : Nat → Nat)34-- Multiple arguments (curried)5#check (fun x y => x + y : Nat → Nat → Nat)6-- Read as: takes a Nat, returns a function Nat → Nat78-- With implicit arguments9#check @List.map10-- {α : Type} → {β : Type} → (α → β) → List α → List βPartial Application
Because multi-argument functions are curried, you can partially apply them:
1def add (x y : Nat) : Nat := x + y23-- Partially apply: fix the first argument4def add5 := add 5 -- add5 : Nat → Nat56#eval add5 3 -- 87#eval add5 10 -- 1589-- Useful with higher-order functions10#eval [1, 2, 3].map (add 10) -- [11, 12, 13]Function Composition
Compose small functions into larger ones using · withFunction.comp or the ∘ operator.
1def double (n : Nat) : Nat := n * 22def inc (n : Nat) : Nat := n + 134def doubleThenInc : Nat → Nat := inc ∘ double5def incThenDouble : Nat → Nat := double ∘ inc67#eval doubleThenInc 3 -- 78#eval incThenDouble 3 -- 8Define a function that squares a number, then converts it to a string.
1def square (n : Nat) : Nat := n * n2def toText (n : Nat) : String := toString n34 def squareToString : Nat → String :=5 toText ∘ square67#eval squareToString 12Where Clauses
For complex functions, use where to define helpers:
1def quadratic (a b c x : Float) : Float := 2 a * x^2 + b * x + c34-- Cleaner with where5def quadratic' (a b c x : Float) : Float := 6 term1 + term2 + term37where8 term1 := a * x^29 term2 := b * x10 term3 := c1112#eval quadratic' 1 2 1 3 -- 16.0