--- title : "BigStep: Big-step semantics" permalink : /BigStep/ --- \begin{code} module plfa.part2.BigStep-2026 where \end{code} # Introduction The preceding chapter defines an intrinsically-typed lambda calculus using de Bruijn indices. It also gives a small-step operational semantics, written `M —→ N`, where each step performs one local computation. In this chapter we introduce a second presentation of evaluation. A _big-step semantics_ relates a closed term directly to the value it computes. We write this relation as `M ⇓ V`: the term `M` evaluates to the value `V`. Compared with small-step reduction, big-step evaluation hides the intermediate states and focuses on the final answer. This makes it a convenient specification of interpreters and evaluators, while small-step reduction remains useful for reasoning about individual computation steps. # Imports \begin{code} import Relation.Binary.PropositionalEquality as Eq open Eq using (_≡_; refl; cong) open import Data.Nat using (ℕ; zero; suc; _<_; z σ = y ↦ 42 λ x → y The lambda case exposes the limitation of this representation. A lambda term with free variables cannot be returned as a closed value by itself; it must be paired with the environment that gives meanings to those variables. That pair is a _closure_, which is the next structure needed for this development. We therefore separate syntactic terms from the values produced by the environment-based evaluator. The type `CVal A` is the type of semantic values of object-language type `A`. Natural numbers are represented by `zero` and `suc` values. Function values are represented by closures: a closure `` `clos γ M `` stores both a body `M` and the environment `γ` in which that body was created. The type `CEnv Γ` is the type of closure environments for context `Γ`. Such an environment maps every variable in `Γ` to a semantic value of the corresponding type. The definitions of `CVal` and `CEnv` depend on each other: closures contain environments, and environments return closure values. This is why `CEnv` is declared before `CVal` and defined afterwards. When evaluation enters the body of a lambda abstraction, the current environment must be extended with a value for the newly bound variable. The function `extend γ v` does exactly that. The newest variable `Z` is mapped to `v`, while an older variable `S x` is looked up in the previous environment `γ`. \begin{code} CEnv : Context → Set data CVal : Type → Set where `zero : CVal `ℕ `suc_ : CVal `ℕ → CVal `ℕ `clos : ∀ {Γ}{A}{B} → CEnv Γ → Γ , A ⊢ B → CVal (A ⇒ B) CEnv Γ = ∀ {A} → Γ ∋ A → CVal A extend : ∀ {Γ}{A} → CEnv Γ → CVal A → CEnv (Γ , A) extend γ v Z = v extend γ v (S x) = γ x \end{code} The environment-based big-step relation is written `γ ∥ M ⇓ V`. It says that, under closure environment `γ`, the term `M` evaluates directly to the semantic value `V`. Unlike the earlier relation `M ⇓ V`, the result is not a closed term but a member of `CVal`. This lets lambda abstraction return a closure without first substituting its free variables away. \begin{code} data _∥_⇓_ : ∀ {Γ}{A} → CEnv Γ → Γ ⊢ A → CVal A → Set where ⇓-‵ : ∀ {Γ}{A}{γ : CEnv Γ} {x : Γ ∋ A} → γ ∥ ` x ⇓ γ x ⇓-ƛ : ∀ {Γ}{A}{B} {γ : CEnv Γ}{M : Γ , A ⊢ B} → γ ∥ ƛ M ⇓ `clos γ M ⇓-· : ∀ {Γ}{A}{B}{γ : CEnv Γ}{L : Γ ⊢ A ⇒ B} {M : Γ ⊢ A} {Γ′}{γ′ : CEnv Γ′}{L′ : Γ′ , A ⊢ B} {V : CVal A}{W : CVal B} → γ ∥ L ⇓ `clos γ′ L′ → γ ∥ M ⇓ V → extend γ′ V ∥ L′ ⇓ W → γ ∥ (L · M) ⇓ W ⇓-zero : ∀ {Γ}{γ : CEnv Γ} → γ ∥ `zero ⇓ `zero ⇓-suc : ∀ {Γ}{γ : CEnv Γ}{M : Γ ⊢ `ℕ}{V : CVal `ℕ} → γ ∥ M ⇓ V → γ ∥ (`suc M) ⇓ (`suc V) ⇓-case-zero : ∀ {Γ}{γ : CEnv Γ}{A}{L}{M : Γ ⊢ A}{N}{V} → γ ∥ L ⇓ `zero → γ ∥ M ⇓ V → γ ∥ case L M N ⇓ V ⇓-case-suc : ∀{Γ}{γ : CEnv Γ} {A}{L}{M : Γ ⊢ A}{N}{V}{W} → γ ∥ L ⇓ (`suc W) → extend γ W ∥ N ⇓ V → γ ∥ case L M N ⇓ V -- not cool to use substitution for recursion :,-( -- but μ M is not a value so we cannot extend γ with it ⇓-μ : ∀ {Γ}{γ : CEnv Γ} {A}{M : Γ , A ⊢ A}{V} → γ ∥ (M [ μ M ]) ⇓ V → γ ∥ (μ M) ⇓ V \end{code} The rules follow the structure of terms. * A variable is evaluated by looking it up in the environment. * A lambda abstraction evaluates to a closure containing the current environment and the lambda body. * An application first evaluates the operator to a closure, then evaluates the argument to a value. The body of the closure is then evaluated in the closure's saved environment, extended with the argument value. * Zero evaluates to zero, and successor evaluates its subterm before rebuilding the successor value. * A case expression evaluates its scrutinee first. If the scrutinee is zero, the zero branch is evaluated in the current environment. If the scrutinee is a successor value, the successor branch is evaluated in an environment extended with the predecessor. * The rule for recursion still uses substitution to unfold `μ M`. This is less satisfying than the other rules because it steps outside the closure-environment discipline, but it is enough for the examples below. Example \begin{code} Ex2+2 : ∅ ⊢ `ℕ Ex2+2 = plus · two · two twoV : CVal `ℕ twoV = `suc (`suc `zero) fourV : CVal `ℕ fourV = `suc (`suc twoV) two⇓ : ∀ {Γ}{γ : CEnv Γ} → γ ∥ two ⇓ twoV two⇓ = ⇓-suc (⇓-suc ⇓-zero) _ : (λ ()) ∥ Ex2+2 ⇓ fourV _ = ⇓-· (⇓-· (⇓-μ ⇓-ƛ) two⇓ ⇓-ƛ) two⇓ (⇓-case-suc ⇓-‵ (⇓-suc (⇓-· (⇓-· (⇓-μ ⇓-ƛ) ⇓-‵ ⇓-ƛ) ⇓-‵ (⇓-case-suc ⇓-‵ (⇓-suc (⇓-· (⇓-· (⇓-μ ⇓-ƛ) ⇓-‵ ⇓-ƛ) ⇓-‵ (⇓-case-zero ⇓-‵ ⇓-‵))))))) \end{code} Discussion about fixed point / recursion. plus : Term plus = μ "+" ⇒ ƛ "m" ⇒ ƛ "n" ⇒ case ` "m" [zero⇒ ` "n" |suc "m" ⇒ `suc (` "+" · ` "m" · ` "n") ] plus′ = ƛ "+" ⇒ ƛ "m" ⇒ ƛ "n" ⇒ case ` "m" [zero⇒ ` "n" |suc "m" ⇒ `suc (` "+" · ` "m" · ` "n") ] x is fixed point of F : F(x) = x plus is fixed point of plus′: plus' (plus) = ƛ "m" ⇒ ƛ "n" ⇒ case ` "m" [zero⇒ ` "n" |suc "m" ⇒ `suc (plus · ` "m" · ` "n") ] = plus % Local Variables: % mode: agda2 % End: