Lean 4 Reference

Lean 4 Crash Course

Programming side of Lean 4 — types, functions, pattern matching, dependent types, and typeclasses.

19Sections
340+Lines of Example
15Topics
Section 01

Values and Types

Everything in Lean has a type. You declare with def:

Lean§1 — Value declarations
def age : Nat := 25
def name : String := "Tomo"
def active : Bool := true
def ratio : Float := 3.14
def ch : Char := 'A'

:= means "is defined as". Type comes after the colon.

Lean infers types when obvious:

Lean§1 — Type inference
def age := 25          -- Lean infers Nat
def name := "Tomo"     -- Lean infers String
Section 02

All Built-in Types

Numeric Types

TypeRangeNotes
Nat0, 1, 2, ...Arbitrary precision, no overflow
Int..., -2, -1, 0, 1, 2, ...Arbitrary precision
Float64-bit IEEE 7543.14, 1.0e10
UInt80 to 255Fixed-width
UInt160 to 65535Fixed-width
UInt320 to 2³²-1Fixed-width
UInt640 to 2⁶⁴-1Fixed-width
Int8-128 to 127Fixed-width signed
Int16-32768 to 32767Fixed-width signed
Int32-2³¹ to 2³¹-1Fixed-width signed
Int64-2⁶³ to 2⁶³-1Fixed-width signed
Fin n0 to n-1Bounded natural, carries proof

Numeric Operations

Lean§2 — Arithmetic
-- Arithmetic
#eval 10 + 3       -- 13
#eval 10 - 3       -- 7    (Nat subtraction floors at 0: 3 - 10 = 0)
#eval 10 * 3       -- 30
#eval 10 / 3       -- 3    (integer division)
#eval 10 % 3       -- 1    (modulo)

-- Comparison (returns Bool)
#eval 3 < 5        -- true
#eval 3 > 5        -- false
#eval 3 <= 3       -- true
#eval 3 >= 5       -- false
#eval 3 == 5       -- false
#eval 3 != 5       -- true

-- Conversion
#eval (5 : Int)           -- Int from Nat
#eval Int.toNat (-3)      -- 0 (clamps)
#eval Int.toNat 5         -- 5
#eval Nat.toFloat 42      -- 42.0

-- Nat-specific
#eval Nat.min 3 5    -- 3
#eval Nat.max 3 5    -- 5
#eval Nat.pow 2 10   -- 1024
#eval Nat.log2 1024  -- 10
#eval Nat.gcd 12 8   -- 4
Warning about Nat subtraction: Nat has no negatives, so subtraction floors at zero. 5 - 3 = 2 but 3 - 5 = 0 (not -2!). Use Int if you need negatives.

Bool

Lean§2 — Bool operations
#eval true && false    -- false (and)
#eval true || false    -- true  (or)
#eval !true            -- false (not)
#eval true == false    -- false

String

Lean§2 — String operations
def s := "hello"

-- Info
#eval s.length          -- 5
#eval s.isEmpty         -- false
#eval "".isEmpty        -- true

-- Combine
#eval s ++ " world"     -- "hello world"
#eval s.append " world" -- "hello world"
#eval String.join ["a", "b", "c"]  -- "abc"
#eval ",".intercalate ["a", "b"]   -- "a,b"

-- Search
#eval s.startsWith "he" -- true
#eval s.endsWith "lo"   -- true
#eval s.contains 'l'    -- true

-- Transform
#eval s.toUpper         -- "HELLO"
#eval s.toLower         -- "hello"
#eval s.trim            -- removes whitespace
#eval s.replace "l" "r" -- "herro"
#eval s.drop 2          -- "llo"
#eval s.take 3          -- "hel"

-- Convert
#eval s.toList          -- ['h', 'e', 'l', 'l', 'o']
#eval s.toUTF8          -- ByteArray
#eval toString 42       -- "42"

-- Split
#eval "a,b,c".splitOn ","     -- ["a", "b", "c"]

-- String interpolation
#eval s!"1 + 1 = {1 + 1}"    -- "1 + 1 = 2"

Char

Lean§2 — Char operations
#eval 'A'.toNat     -- 65
#eval 'a'.isAlpha   -- true
#eval '3'.isDigit   -- true
#eval 'A'.isUpper   -- true
#eval 'a'.isLower   -- true
#eval 'A'.toLower   -- 'a'
#eval Char.ofNat 65 -- 'A'

ByteArray

Lean§2 — ByteArray operations
def bs := ByteArray.mk #[0x48, 0x65]

#eval bs.size              -- 2
#eval bs.get! 0            -- 72 (0x48)
#eval bs.push 0x6C         -- appends byte
#eval bs.extract 0 1       -- sub-array [start, end)
#eval bs.append bs         -- concatenate
#eval bs ++ bs             -- same as append
#eval ByteArray.empty      -- empty
#eval bs.toList            -- [72, 101]
Section 03

Functions

Basic

Lean§3 — Function definition
def add (x : Nat) (y : Nat) : Nat :=
  x + y

#eval add 3 5    -- 8

Each argument gets its own parentheses. Call with spaces, no commas, no parens.

Shorthand for same type

Lean§3 — Shorthand syntax
-- These are identical:
def add (x : Nat) (y : Nat) : Nat := x + y
def add (x y : Nat) : Nat := x + y

(x y : Nat) is just shorthand for (x : Nat) (y : Nat). Same thing.

Let bindings

Local variables. The last expression is the return value — no return keyword:

Lean§3 — Let bindings
def compute (x : Nat) : Nat :=
  let doubled := x * 2
  let offset := doubled + 10
  offset                        -- this is returned

where syntax

Lean§3 — where helpers
def distance (x1 y1 x2 y2 : Nat) : Nat :=
  diff x1 x2 + diff y1 y2
where
  def diff (a b : Nat) : Nat := if a > b then a - b else b - a

Anonymous functions (lambda)

Lean§3 — Lambda expressions
-- Full
fun x => x + 1

-- With type
fun (x : Nat) => x + 1

-- Multiple args
fun x y => x + y

-- The · shorthand (type \. in VS Code)
(· + 1)              -- same as fun x => x + 1
(· + ·)              -- same as fun x y => x + y
(· > 3)              -- same as fun x => x > 3

-- These are the same:
[1,2,3].map (· + 10)
[1,2,3].map (fun x => x + 10)

Function types

is the function arrow. Type \to or \r or just ->:

Lean§3 — Function types
-- Nat → Nat means "takes a Nat, returns a Nat"
-- Nat → Nat → Nat means "takes two Nats, returns a Nat"
#check add    -- Nat → Nat → Nat

-- Each constructor IS a function:
inductive Shape where
  | circle : NatShape             -- circle is a function: Nat → Shape
  | rectangle : NatNatShape   -- Nat → Nat → Shape
  | point : Shape                    -- a value (no args)
Section 04

If / Else

if is an expression — it returns a value. Both branches required:

Lean§4 — Conditionals
def max (a b : Nat) : Nat :=
  if a > b then a else b

-- Multi line
def classify (n : Nat) : String :=
  if n == 0 then
    "zero"
  else if n < 10 then
    "small"
  else
    "big"

Dependent if

if h : condition gives you a proof you can pass to other functions:

Lean§4 — Dependent if
if h : b = 0 then
  -- h is a proof that b = 0
  0
else
  -- h is a proof that b ≠ 0
  a / b

-- Function REQUIRES proof that divisor isn't zero
def safeDivide (a b : Nat) (proof : b ≠ 0) : Nat := a / b

if h : b = 0 then 0
else safeDivide a b h     -- h proves b ≠ 0
Section 05

Structure

Lean§5 — Structure definition
structure Point where
  x : Nat
  y : Nat

Three ways to create

Lean§5 — Constructors
def p1 : Point := { x := 3, y := 5 }       -- named fields
def p2 := Point.mk 3 5                       -- .mk constructor (positional)
def p3 : Point := ⟨3, 5-- anonymous constructor

Access and update

Lean§5 — Access & update
#eval p1.x      -- 3
#eval p1.y      -- 5

-- Functional update (creates new value, original unchanged)
def moved := { p1 with x := 10 }
#eval moved.x   -- 10
#eval moved.y   -- 5 (unchanged)

Default values

Lean§5 — Defaults
structure Config where
  port : Nat := 8080
  host : String := "localhost"
  debug : Bool := false

def c : Config := { port := 3000 }    -- host and debug use defaults

Deriving

Lean§5 — Deriving instances
structure Entry where
  name : String
  value : Nat
  deriving DecidableEq, Repr, Inhabited, BEq, Hashable
DerivedWhat you get
DecidableEquse == in proofs, enables native_decide
BEquse == in normal code
Repr#eval can print it
Inhabitedhas a default value (default : Entry)
Hashablecan use as HashMap key

Escaped field names

Lean§5 — French quotes
structure Commit where
  «from» : PubKey        -- from is reserved in Lean
  type : String
  seq : Nat

-- Access:
commit.«from»
Section 06

Inductive Types (Enums / Tagged Unions)

Simple enum

Lean§6 — Enum
inductive Direction where
  | north | south | east | west

With data

Lean§6 — Tagged union
inductive Shape where
  | circle    : NatShape              -- carries a radius
  | rectangle : NatNatShape        -- carries width and height
  | point     : Shape                    -- carries nothing

-- Constructing = calling the function:
def s1 := Shape.circle 10
def s2 := Shape.rectangle 3 5
def s3 := Shape.point

Pattern matching

Lean§6 — Pattern matching
-- Function syntax: | patterns directly
def describe : ShapeString
  | .circle r      => "circle with radius " ++ toString r
  | .rectangle w h => toString w ++ "x" ++ toString h
  | .point         => "point"

-- Match syntax: explicit match expression
def describe (s : Shape) : String :=
  match s with
  | .circle r      => "circle with radius " ++ toString r
  | .rectangle w h => toString w ++ "x" ++ toString h
  | .point         => "point"

.circle is shorthand for Shape.circle — Lean infers the type. Pattern matching extracts the data. Exhaustive: you MUST handle every constructor.

Pattern matching multiple arguments

Lean§6 — Multi-arg patterns
def canDo : String → Op → Bool
  | "Owner", .C => true
  | "Owner", .R => true
  | "Any",   .R => true
  | _,       _  => false      -- _ means "anything else"

Recursive types

Lean§6 — Recursive inductive
inductive Tree where
  | leaf : NatTree                 -- end point, holds a number
  | node : TreeTreeTree         -- branches into two sub-trees

-- Recursive function walks the tree:
def Tree.sum : TreeNat
  | .leaf n   => n                    -- base case: return the number
  | .node l r => l.sum + r.sum        -- recursive: sum both children
Section 07

Option

Option A = either some value or none. Lean's way of handling "might not exist":

Lean§7 — Option operations
def x : Option Nat := some 42
def y : Option Nat := none

-- Extract
#eval x.get!             -- 42 (panics on none — dangerous)
#eval x.getD 0           -- 42 (returns default on none — safe)
#eval y.getD 0           -- 0

-- Check
#eval x.isSome           -- true
#eval x.isNone           -- false

-- Transform without unwrapping
#eval x.map (fun n => n + 1)     -- some 43
#eval y.map (fun n => n + 1)     -- none (function never runs)

-- Filter
#eval x.filter (fun n => n > 10) -- some 42
#eval x.filter (fun n => n > 50) -- none

-- Convert
#eval x.toList           -- [42]
#eval y.toList           -- []

Do notation: clean chaining

Lean§7 — Do notation with Option
def addFirstTwo (xs : List Nat) : Option Nat := do
  let a ← xs.get? 0      -- ← unwraps Option. If none, whole do returns none
  let b ← xs.get? 1
  return a + b

#eval addFirstTwo [10, 20, 30]    -- some 30
#eval addFirstTwo [10]             -- none (no second element)
#eval addFirstTwo []               -- none

Naming convention

SuffixReturnsOn failure
?Optionnone
!raw valuepanics
Draw valuereturns default
Section 08

List

Lean§8 — Creating lists
def xs : List Nat := [1, 2, 3, 4, 5]
def empty : List Nat := []
def range := List.range 5              -- [0, 1, 2, 3, 4]
def repeated := List.replicate 3 "x"   -- ["x", "x", "x"]
Lean§8 — Transforming
#eval xs.map (fun x => x * 2)           -- [2, 4, 6, 8, 10]
#eval xs.filter (fun x => x > 3)        -- [4, 5]
#eval xs.reverse                         -- [5, 4, 3, 2, 1]
#eval xs.take 3                          -- [1, 2, 3]
#eval xs.drop 3                          -- [4, 5]
#eval xs.zip [10, 20, 30]               -- [(1,10), (2,20), (3,30)]
#eval xs.enum                            -- [(0,1), (1,2), (2,3), ...]
Lean§8 — Reducing & querying
#eval xs.foldl (fun acc x => acc + x) 0  -- 15 (sum)
#eval xs.any (fun x => x > 4)     -- true
#eval xs.all (fun x => x > 0)     -- true
#eval xs.contains 3                -- true
#eval xs.find? (fun x => x > 3)   -- some 4
Lean§8 — Building & cons
#eval [1, 2] ++ [3, 4]            -- [1, 2, 3, 4] (append)
#eval 0 :: [1, 2, 3]              -- [0, 1, 2, 3] (cons: prepend one)
#eval [[1,2], [3,4]].join         -- [1, 2, 3, 4] (flatten)
#eval [3, 1, 4, 1, 5].mergeSort   -- [1, 1, 3, 4, 5]

-- Cons in pattern matching splits a list:
def sum : List NatNat
  | []      => 0                -- empty list
  | x :: xs => x + sum xs       -- x is first, xs is the rest
Section 09

Array

Like List but backed by contiguous memory. Better for random access. Literal uses #[...]:

Lean§9 — Array operations
def arr : Array Nat := #[1, 2, 3, 4, 5]

#eval arr.size         -- 5 (Array uses .size, List uses .length)
#eval arr.get! 2       -- 3
#eval arr.push 6       -- #[1, 2, 3, 4, 5, 6]
#eval arr.pop          -- #[1, 2, 3, 4]
#eval arr.set! 0 99    -- #[99, 2, 3, 4, 5]
#eval arr.map (fun x => x * 2)   -- #[2, 4, 6, 8, 10]
#eval arr.filter (fun x => x > 3)  -- #[4, 5]
#eval arr.reverse      -- #[5, 4, 3, 2, 1]

List vs Array

ListArray
Literal[1, 2, 3]#[1, 2, 3]
Length.length.size
Prepend0 :: xs O(1)O(n)
Index.get? i O(n).get! i O(1)
Pattern matchx :: xscan't
Use forproofs, specsperformance, IO
Section 10

Tuple / Product

Lean§10 — Tuples
def pair : Nat × String := (42, "hello")

#eval pair.1    -- 42
#eval pair.2    -- "hello"

-- Destructure
let (a, b) := pair
#eval a    -- 42

-- Triple
def triple : Nat × String × Bool := (1, "hi", true)
#eval triple.1       -- 1
#eval triple.2.1     -- "hi"
#eval triple.2.2     -- true

× (type \times or \x) is the product type. In practice, use structure with named fields instead — .1 and .2 are unreadable.

Section 11

HashMap

Lean§11 — HashMap
open Std

def m := HashMap.empty.insert "a" 1 |>.insert "b" 2

#eval m.find? "a"       -- some 1
#eval m.find? "z"       -- none
#eval m.contains "a"    -- true
#eval m.size            -- 2
#eval m.erase "a"       -- removes key
#eval m.toList          -- [("a", 1), ("b", 2)]

The pipe operator |>

|> takes the result of the left side and feeds it as input to the right:

Lean§11 — Pipe operator
-- Take 5, then toString, then get length
#eval 5 |> toString |> String.length    -- 1

-- Sort, then reverse, then get first
#eval [3, 1, 2] |>.mergeSort |>.reverse |>.head!   -- 3
Section 12

Dependent Types

The core idea: a type can depend on a VALUE.

Lean§12 — Size-indexed types
structure ByteVec (n : Nat) where
  data : ByteArray
  size_eq : data.size = n

abbrev Hash := ByteVec 32       -- exactly 32 bytes
abbrev PubKey := ByteVec 32     -- exactly 32 bytes
abbrev Signature := ByteVec 64  -- exactly 64 bytes

You cannot pass a Signature where a Hash is expected. The compiler rejects it.

You can encode any invariant

Lean§12 — Proof-carrying types
-- Non-empty list
structure NonEmptyList (a : Type) where
  data : List a
  nonempty : data.length > 0

-- Bounded number
structure Port where
  value : Nat
  low : value >= 1
  high : value <= 65535

-- Valid email
structure Email where
  value : String
  hasAt : value.contains '@' = true

Can't construct a Port with value 0. Can't construct an Email without @. The compiler enforces it — not runtime, not tests.

Vec: length-indexed list

Lean§12 — Vec type
inductive Vec (a : Type) : NatType where
  | nil  : Vec a 0                        -- empty = length 0
  | cons : a → Vec a n → Vec a (n + 1)    -- prepend = length + 1

-- Only works on non-empty Vec (n + 1, not 0)
def head : Vec a (n + 1) → a
  | .cons x _ => x

-- Zipping requires SAME length (both n)
def zip : Vec a n → Vec b n → Vec (a × b) n
  | .nil, .nil => .nil
  | .cons a as, .cons b bs => .cons (a, b) (zip as bs)

head on empty Vec is a compile error, not a crash. zip with different lengths is a compile error. The impossible states don't exist.

Section 13

Namespace, open, section

Lean§13 — Namespacing
-- namespace — adds a prefix
namespace Utils
  def helper := 42
end Utils
#eval Utils.helper     -- need prefix

-- open — removes the prefix
open Utils
#eval helper           -- no prefix

-- section — groups without prefix
section MyProofs
  variable (n : Nat)       -- n available to all in section

  theorem ex1 : n + 0 = n := by simp
  theorem ex2 : n * 1 = n := by simp
end MyProofs
-- n is gone here
Section 14

Type Aliases

Lean§14 — abbrev vs def
-- Transparent alias (compiler sees through it)
abbrev Hash := ByteVec 32
-- Hash and ByteVec 32 are interchangeable everywhere

-- Opaque alias (distinct type)
def Score := Nat
-- Score and Nat are NOT interchangeable
Section 15

Type Classes and instance

Define shared behavior:

Lean§15 — Typeclass basics
class Printable (a : Type) where
  print : a → String

instance : Printable Nat where
  print n := toString n

instance : Printable Bool where
  print b := if b then "yes" else "no"

-- [Printable a] means "a must have a Printable instance"
def show [Printable a] (x : a) : String := Printable.print x

#eval show 42        -- "42"
#eval show true      -- "yes"

Built-in type classes

ClassWhat it provides
DecidableEq== in proofs, decide
BEq== operator
Repr#eval can print
Inhabiteddefault value
HashableHashMap keys
Ord<, >, compare
Add+ operator
ToStringtoString conversion

Typeclass fields with proof obligations

A typeclass can require proofs, not just data. The ENC spec uses this to enforce correctness at the type level:

Lean§15 — Proof obligations
class AppManifest (A : Type) where
  name          : String
  schema        : Schema
  eventTypes    : List String
  -- Proof obligations — must be satisfied by every instance
  all_creatable : ∀ et, et ∈ eventTypes → ∃ role, schema.permits role et .C = true
  roles_valid   : ∀ r, r ∈ customRoles → r ≠ "Self" ∧ r ≠ "Owner"

class AppMiddleware (K : Type) where
  encrypt   : K → ByteArrayByteArray
  decrypt   : K → ByteArrayOption ByteArray
  -- Roundtrip proof: decrypt(encrypt(m)) = m
  roundtrip : ∀ k m, decrypt k (encrypt k m) = some m

An instance must provide both the implementations AND the proofs:

Lean§15 — Instance with proofs
instance : AppManifest TimelineConfig where
  name := "timeline"
  schema := timelineSchema
  eventTypes := ["Timeline_Post", "Timeline_Profile", ...]
  all_creatable := by
    intro et het
    simp [List.Mem] at het
    rcases het with rfl | rfl | rfl
    · exact ⟨"Owner", by native_decide⟩
    · exact ⟨"Owner", by native_decide⟩
    · exact ⟨"Any", by native_decide⟩
  roles_valid := by
    intro r hr; simp [List.Mem] at hr
    rcases hr with rfl | rfl
    · constructor <;> decide
    · constructor <;> decide

extends — typeclass inheritance

extends bundles multiple typeclasses. An instance of the child satisfies all parents:

Lean§15 — extends
class DirectMiddleware (K : Type) extends AppMiddleware K where
  agree     : ByteVec 32 → PubKey → K
  agree_sym : ∀ a b, agree a b = agree b a

-- ENCApp bundles everything — if it exists, the app is valid
class ENCApp (A W K Idx Q S : Type) extends
    AppManifest A, AppSDK A W, AppMiddleware K,
    AppDataView A Idx Q, AppState S, AppComponents A W

noncomputable

When a definition depends on axioms (like sha256, schnorr_sign), Lean can't generate executable code. Mark it noncomputable:

Lean§15 — noncomputable
-- sha256 is an axiom — no implementation exists in Lean
axiom sha256 : ByteArrayHash

-- Anything using sha256 must be noncomputable
noncomputable def H (fields : List CborField) : Hash :=
  sha256 (cborEncode fields)

-- Instances using axiomatized functions also need noncomputable
noncomputable instance : AppSDK TimelineConfig TimelineWrite where
  buildCommit := fun w id enc exp => ...    -- uses axiom-based SDK functions

noncomputable means "this definition is correct but Lean won't try to run it." You can still prove theorems about it — proofs don't need to execute the code, just reason about it.

Three-layer hierarchy in the ENC spec

Lean§15 — ENC hierarchy
FTA (abstract)     class FTA.CryptoSuite, class FTA.App
  ↓ instance
ENC (protocol)     class AppManifest, AppSDK, AppMiddleware, ...
  ↓ instance
Apps               instance : AppManifest TimelineConfig, ...

FTA defines abstract typeclasses (what any protocol must provide). ENC provides instances for FTA and defines its own typeclasses (what any ENC app must provide). Each app (Timeline, Registry, DM, Chat) provides instances for the ENC typeclasses.

Section 16

do Notation and IO

For side effects:

Lean§16 — IO & do
def main : IO Unit := do
  IO.println "What is your name?"
  let name ← IO.getStdin >>= (·.getLine)
  IO.println s!"Hello, {name}"

do enables imperative style. extracts a value from IO. s!"..." is string interpolation:

Lean§16 — String interpolation
#eval s!"1 + 1 = {1 + 1}"    -- "1 + 1 = 2"
#eval s!"name: {"Tomo"}"      -- "name: Tomo"
Section 17

import and Project Structure

Lean§17 — Imports
import Enc.Core.Primitives       -- load module
open Enc.RBAC                     -- bring names into scope

File path → module name:

Lean§17 — File mapping
Enc/Core/Primitives.lean   →  import Enc.Core.Primitives
Enc/Apps/Timeline/App.lean →  import Enc.Apps.Timeline.App

-- Barrel re-export:
-- Enc/Core/Primitives.lean
import Enc.Core.Primitives.Types
import Enc.Core.Primitives.Crypto
import Enc.Core.Primitives.Hash
-- importing Primitives imports all three
Section 18

Exploration Tools

Lean§18 — #check, #eval, #print
#check Nat                -- Nat : Type           (what is this?)
#check (3 : Nat)          -- 3 : Nat
#check Nat.add            -- Nat → Nat → Nat     (function signature)
#check @List.map          -- full type with all implicit args

#eval 2 + 3              -- 5                     (run and print)
#eval [1,2,3].map (fun x => x * 2)   -- [2, 4, 6]

#print Nat.add            -- shows the definition
#print axioms H_injective -- shows which axioms a theorem depends on

Open the Infoview panel to see these results inline: Ctrl+Shift+P → "Lean 4: Infoview: Toggle"

Section 19

Typing Unicode Symbols

In VS Code with the Lean 4 extension, type backslash sequences:

TypeGetName
\. or \cdot·middle dot (anonymous arg)
\to or \r or ->arrow
\forall or \allfor all
\exists or \exexists
\andand
\oror
\not or \neg¬not
\nenot equal
\leless or equal
\gegreater or equal
\iffif and only if
\x or \times×product type
\<angle bracket open
\>angle bracket close
\lamλlambda

You can always avoid unicode:

UnicodeASCII alternative
·use fun x => instead
->
forall
<=
>=
!=
/\
\/
⟨a, b⟩use .mk a b instead
Reference

Cheat Sheet: Symbols

SymbolMeaning
:=is defined as
/ ->function type
×product type (tuple)
·anonymous arg: (· + 1) = fun x => x + 1
⟨a, b⟩anonymous constructor (= .mk a b)
«word»escape reserved keyword
_wildcard / infer this
|>pipe (feed result to next)
::cons (prepend to list)
++append
unwrap in do block
s!"..."string interpolation
#evalrun and print
#checkshow type
#printshow definition

Cheat Sheet: Keywords

KeywordPurpose
defdefine function or value
structurerecord type with named fields
inductiveenum / tagged union
abbrevtransparent type alias
classtype class (shared interface)
instancetype class implementation
extendstypeclass inheritance
noncomputabledefinition uses axioms (can't execute, can prove)
namespace / endgrouping with prefix
openbring names into scope
section / endgrouping without prefix
importload module
derivingauto-generate instances
wherehelper definitions
letlocal binding
match / withpattern match
if / then / elseconditional
doimperative block
funanonymous function