🐱 Path Induction (J eliminator) — an intuitive guide with cat-themed examples

Posted in: Tutorials & Guides  |  🏷️ hott agda dep-types
Showing posts 1–9 of 9
⬇ Jump to reply
😸
CubicalCat
★ Cubical Kitty
Tutorial Author
♦ Meow-derator
Posts: 2,847
Joined: 3 yrs ago
Rep: +4192
Agda: expert
#1

🐾 Purrr, hello CGPA! nya~

I've seen a lot of people in #type-theory-help get confused by J — the path induction eliminator. It seems scary, but I promise it's just regular old induction wearing a spooky mask, like a cat in a ghost costume. 👻🐱

This tutorial is going to:

  • Build intuition for what J actually says
  • Show the "teleporting cat" mental model for path induction
  • Derive transport, ap, and sym from J in Agda
  • Explain based vs. full path induction
  • Show why the computation rule matters

Let's go! nya~

§1. What is an Identity Type?

In dependent type theory, the identity type x ≡ y (sometimes written Id A x y or x =ₐ y) is the type whose inhabitants are proofs that x and y are equal. This is propositional equality — it lives inside the type theory rather than being a meta-level judgment.

The key difference from judgemental equality (x ≡ y as definitional reduction) is that propositional equality is a type you can pass around, return from functions, and reason about.

The only built-in constructor for identity types is reflexivity:

-- In Agda (standard library style):
data _≡_ {A : Set} : A  A  Set where
  refl : {x : A}  x  x

refl is the proof that any term equals itself. That's the only constructor. No other constructor can be given a priori.

🐱 Cat Analogy: The "Same Cat" Proof
Imagine you have a cat named Socks. The statement "Socks ≡ Socks" is obviously true — you just point at the cat and say refl. The statement "Socks ≡ Mittens" (where Mittens is a different cat) might be provable by some other path in your type, but refl alone won't do it — you'd need an actual proof of their equality.

§2. Path Induction — The Core Idea

Here's the key insight: since refl is the only constructor for _≡_, it is an inductive type, and we can do induction on its elements, just like we do induction on .

For , induction says: to prove P n for all n, it suffices to prove P zero and the step case. For identity types, there's only one constructor (refl), so the induction principle says:

💡 Path Induction (Informal)
To prove a property C x y p for all x y : A and all p : x ≡ y, it suffices to prove it only in the case where x and y are the same term and p is refl.

This is the J eliminator. Formally, in Agda:

J : {A : Set}
    (C : (x y : A)  x  y  Set)    -- the motive / predicate
    (d : (x : A)  C x x refl)           -- proof in the refl case
    {x y : A} (p : x  y)             -- the actual path
     C x y p                             -- result
J C d refl = d _                         -- computation rule
🐱 Cat Analogy: The Teleporting Cat

Imagine you have two cats, Socks (x) and Mittens (y), and someone hands you a proof p : Socks ≡ Mittens — a "path" connecting them. Path induction says: to reason about any such path, you may assume the cats are actually the same cat sitting in the same spot (x = y = refl).

Why? Because if you only built cats via the refl constructor, the only path you can ever construct is a cat sitting next to itself! So any arbitrary path is secretly just a dressed-up version of that. The cat never actually moved — it just looks like it did from the outside. 🐾→🐾

§3. Understanding the Motive C

The scariest-looking part of J for beginners is the motive argument C : (x y : A) → x ≡ y → Set. It's a dependent predicate that can mention both endpoints of the path AND the path itself.

For each derivation with J, you need to figure out the right C. This is sometimes called "picking the motive" and it's genuinely the hard part. Let me show you by example.

✅ Pro Tip: How to Pick C

Start from your goal. Your goal mentions x, y, and p : x ≡ y. To form C, abstract over all three: replace x with a free variable, y with another free variable, and p with the path variable. Then check that the refl case becomes obviously provable.

§4. Deriving transport from J

transport is one of the most fundamental operations you can derive from J. Given a proof that x ≡ y and a value of type P x, transport produces a value of type P y. Conceptually, you "slide" a value along the path.

🐾 Cat Analogy: Transporting the Kitty Bowl
If Socks and Mittens are proven to be equal (p : Socks ≡ Mittens), and Socks has a food bowl (bowl : FoodBowl Socks), then you can transport the bowl over to Mittens (transport FoodBowl p bowl : FoodBowl Mittens). The bowl travels along the path! 🥣→🐱
-- transport : given P x and a path x ≡ y, produce P y
transport : {A : Set}
             (P : A  Set)
             {x y : A}
             (p : x  y)
              P x
              P y
transport P p px =
  J  x y _  P x  P y)   -- motive C x y p = (P x → P y)
      _ px  px)               -- refl case: identity function
     p                           -- the path
     px                          -- the value to transport

Notice the motive: C x y _ = P x → P y. When we substitute y := x and p := refl, the motive becomes P x → P x, which is just the identity — trivially provable! The computation rule gives us transport P refl px = px.

§5. Deriving ap (cong) from J

ap (also called cong in Agda's stdlib) says that functions respect propositional equality: if x ≡ y then f x ≡ f y. It's the lifting of a function to paths.

🐾 Cat Analogy: ap is "applying a collar to equal cats"
If Socks ≡ Mittens, and you have a function addCollar : Cat → CatWithCollar, then ap addCollar p : addCollar Socks ≡ addCollar Mittens. Equal cats get equal collars! 🐱🎀=🐱🎀
ap : {A B : Set}
     (f : A  B)
     {x y : A}
     (p : x  y)
      f x  f y
ap f p =
  J  x y _  f x  f y)    -- motive: f x ≡ f y
      _  refl)               -- refl case: f x ≡ f x by refl
     p

The motive collapses to f x ≡ f x in the refl case, which is immediately refl. 🎉

§6. Deriving sym (path reversal) from J

sym says equality is symmetric: if x ≡ y then y ≡ x. In HoTT terms, you can reverse a path.

🐾 Cat Analogy: The Path Runs Both Ways
If there's a cat-tunnel from Socks to Mittens, a cat can walk back through the same tunnel. sym p : y ≡ x is just the reversed tunnel! 🐱⬅️➡️🐱
sym : {A : Set} {x y : A}
       x  y
       y  x
sym p =
  J  x y _  y  x)       -- motive: y ≡ x  (note: reversed!)
      _  refl)              -- refl case: x ≡ x by refl
     p

The motive C x y _ = y ≡ x reduces to C x x _ = x ≡ x in the refl case, which is refl. The reversal comes "for free" from the motive being backwards!

§7. Bonus: trans (path concatenation)

Path transitivity — if x ≡ y and y ≡ z then x ≡ z — is also derivable:

trans : {A : Set} {x y z : A}
         x  y
         y  z
         x  z
trans p q =
  J  x y _  y  z  x  z)  -- motive: y ≡ z → x ≡ z
      _ q  q)                  -- refl case: identity on q
     p q                          -- apply to q

When p = refl, x = y, so y ≡ z → x ≡ z collapses to x ≡ z → x ≡ z, which is just the identity function applied to q. So trans refl q = q. nya~

§8. Based Path Induction (the "j" rule)

There's a variant of J called based path induction (or the j eliminator with lowercase), where instead of universally quantifying over both endpoints, you fix one endpoint:

-- Based J: fix the left endpoint, vary only the right
J-based : {A : Set} {x : A}
          (C : (y : A)  x  y  Set)   -- motive with x fixed
          (d : C x refl)                    -- proof at (x, refl)
          {y : A} (p : x  y)
           C y p
J-based C d refl = d
🐱 Based J — The Cat Stays Home
In based path induction, one cat (x) stays at home. You're proving something about all paths starting from x. It's like Socks stays in the living room, and we reason about all possible tunnels Socks could travel through. The other endpoint y varies freely, but Socks's starting position is fixed.

The full J and based J are actually inter-derivable — you can prove each from the other. Based J is sometimes easier to use because you don't need to abstract over the left endpoint.

§9. The Computation Rule (Beta for J)

A crucial property of J is its computation rule: applying J to a refl path reduces definitionally:

J-β:
J C d {x} {x} refl ≡ d x
-- This is a DEFINITIONAL equality (judgemental), not just propositional!

This matters a lot in practice. In standard Agda with data _≡_, the above holds definitionally because refl pattern matching reduces immediately. In Cubical Agda with path types, J is defined differently and the computation rule only holds propositionally (up to a path). This is one of the tradeoffs of cubical type theory.

§10. Full Agda Example — Putting It Together

module CatPaths where

open import Agda.Builtin.Equality
  -- imports _≡_ with constructor refl,
  -- and the built-in eliminator J

-- A type of cats (abstract, just for illustration)
postulate
  Cat : Set
  FoodBowl : Cat  Set
  Collar : Cat  Set
  addCollar : Cat  Cat   -- gives cat a collar (returns new cat record)

-- J eliminator (can be defined manually even if built-in exists)
J : {A : Set}
    (C : (x y : A)  x  y  Set)
     ((x : A)  C x x refl)
     {x y : A}  x  y
     C x y
J _ d refl = d _

-- transport: slide a value along a path
transport : {A : Set} (P : A  Set)
             {x y : A}  x  y  P x  P y
transport P p px = J  x y _  P x  P y)  _ v  v) p px

-- ap: apply a function to a path
ap : {A B : Set} (f : A  B)
     {x y : A}  x  y  f x  f y
ap f p = J  x y _  f x  f y)  _  refl) p

-- sym: reverse a path
sym : {A : Set} {x y : A}  x  y  y  x
sym p = J  x y _  y  x)  _  refl) p

-- Useful derived lemma: transport refl is identity
transport-refl : {A : Set} (P : A  Set) {x : A} (v : P x)
                   transport P refl v  v
transport-refl P v = refl   -- follows from J-β!

-- The cat example: transport a food bowl along an equality
transportBowl : {c₁ c₂ : Cat}
                  c₁  c₂
                  FoodBowl c₁
                  FoodBowl c₂
transportBowl = transport FoodBowl

-- The collar example: equal cats get equal collars
equalCatsEqualCollars : {c₁ c₂ : Cat}
                           c₁  c₂
                           addCollar c₁  addCollar c₂
equalCatsEqualCollars = ap addCollar

§11. Common Mistakes & FAQ

Q: Why can't I just pattern match on p : x ≡ y directly without J?
A: In standard Agda, you can pattern match on refl, and Agda's dot patterns (.x) handle the unification. But you need x and y to be free variables — if they're fixed, Agda's K axiom may be invoked. J is the principled way that works in all type theories including HoTT.

Q: When should I use transport vs ap?
A: Use transport P p when you have a dependent type family P : A → Set and need to slide a value. Use ap f p when f : A → B is a function to a fixed type B. ap f p is actually transport (λ y → f x ≡ f y) p refl in disguise.

Q: Is J the same as pattern matching on refl?
A: Essentially yes, in Martin-Löf style type theory with K turned off. J is the formal elimination rule; pattern matching on refl in Agda is syntactic sugar that desugars to J (approximately). In HoTT/cubical settings they differ in their computational content.

🐾 Summary
  • J is the elimination rule for identity types — induction on paths
  • To use it: pick a motive C, prove the refl case, apply to your path
  • transport = slide values along paths (motive: P x → P y)
  • ap = apply functions to paths (motive: f x ≡ f y)
  • sym = reverse a path (motive: y ≡ x)
  • All these derive from J, which derives from having only refl as a constructor!

That's it for the main tutorial! I'll be around to answer questions. If there's interest I can write a follow-up on heterogeneous paths, PathOver, and how transport interacts with function types. nya~ 🐾

— CubicalCat | "To understand recursion, one must first understand cats."
📌 Agda Setup Guide for Beginners | HoTT Reading List
👍 Like (87) 💬 Quote 📌 Pin ⚑ Report Views: 3,291
🐈
KittenKleisli
Monad Purrveyor
Posts: 541
Joined: 1 yr ago
Rep: +762
#2

this is the best explanation i've ever read, thank you so much!! nya 😭🐱

One question: in §3 you say "abstract over all three" when picking the motive. But when I try to derive sym, I keep writing the motive as λ x y _ → x ≡ y instead of λ x y _ → y ≡ x (the correct one). How do I remember to flip it?

Is there a systematic procedure, or is it just practice?

👍 Like (23) 💬 Quote ⚑ Report
😸
CubicalCat
★ Cubical Kitty
Tutorial Author
♦ Meow-derator
Posts: 2,847
Joined: 3 yrs ago
Rep: +4192
#3
KittenKleisli wrote:
How do I remember to flip it? Is there a systematic procedure?

Great question! There's definitely a procedure. The trick is to start from your goal and work backwards:

For sym, your goal is to produce y ≡ x given p : x ≡ y. So you need C x y p = y ≡ x — the type you want to produce when J is done. Then check: does the refl case type-check? If y := x and p := refl, then C x x refl = x ≡ x, which is inhabited by refl. ✅

If you had written C x y _ = x ≡ y, then J C d p : x ≡ y, which just gives you back the path you started with — not a reversal! The motive determines what J returns, so the motive IS your output type.

🐱 Motive Recipe
  1. Write down your desired output type, e.g. y ≡ x
  2. Replace the specific x, y, p with lambda-bound variables: λ a b q → b ≡ a
  3. Check: substituting b := a, q := refl gives a ≡ a
  4. That's your motive! nya~

After a while it becomes muscle memory 🐾

👍 Like (31) 💬 Quote ⚑ Report
🐾
TypedTabby
λ-calcumeow-s enjoyer
Posts: 219
Joined: 8 mo ago
Rep: +314
#4

nyaaa I've been struggling with this for weeks and this finally made it click!! the "teleporting cat" model is so cute and helpful 😭

Question about the computation rule in §9: you said in Cubical Agda it only holds propositionally, not definitionally. Does that mean if I write:

test : transport FoodBowl refl bowl  bowl
test = refl   -- does this typecheck?

...it would fail in Cubical Agda? Or only if I use path types instead of the Id type?

👍 Like (14) 💬 Quote ⚑ Report
😸
CubicalCat
★ Cubical Kitty
Tutorial Author
♦ Meow-derator
Posts: 2,847
Joined: 3 yrs ago
Rep: +4192
#5
TypedTabby wrote:
does this typecheck in Cubical Agda?

It depends on which identity type you're using! This is a common point of confusion.

In standard Agda (with the inductive data _≡_): yes, test = refl typechecks because the pattern match on refl reduces definitionally.

In Cubical Agda with path types (_≡_ as I → A): transport is defined via the transp primitive, and transp (λ _ → A) i0 x does not reduce to x definitionally in all cases. You'd need transportRefl:

-- In Cubical Agda:
open import Cubical.Foundations.Prelude

test : {A : Type} {x : A}
         transport refl x  x
test = transportRefl _   -- NOT refl, but a propositional path

However, if you use Cubical Agda's Id type (in Cubical.Core.Id) which has a J eliminator that computes definitionally on refl — then test = refl works again.

In the standard (non-cubical) world: don't worry about this! Just use the inductive _≡_ and pattern matching and everything reduces as expected. nya~

See also: our thread on transport in Cubical Agda for more gory details.

— CubicalCat
👍 Like (22) 💬 Quote ⚑ Report
🐱
MeowndAlgebra
HoTT Purrsuit
Posts: 1,102
Joined: 2 yrs ago
Rep: +1,588
#6

Excellent tutorial as always CubicalCat 🐾 One thing I'd add for people wondering about the homotopy theory perspective:

The "teleporting cat" intuition corresponds to the topological fact that the path space based at a point x (i.e., all paths starting from x) is contractible — it can be continuously deformed so that every path shrinks back to the constant path at x. This is why you can always "assume" the path is refl without loss of generality.

More precisely, in HoTT, the type Σ (y : A), x ≡ y (a point together with a path from x) is contractible, with center (x, refl). Path induction is essentially the recursion principle for this contractible type!

There's a beautiful explanation of this in the HoTT Book (Lemma 3.11.8, also sometimes called "path space contraction"). The intuition is: if every based path space is contractible, then to prove anything about an arbitrary based path, it suffices to prove it for the trivial path — which is exactly what J says. 🐱✨

This also explains why K (which says all loops are refl) is NOT derivable from J alone — K would require the loop space x ≡ x to also be contractible, which fails for types like .

👍 Like (47) 💬 Quote ⚑ Report
😺
PurrfectlyTyped
Newcat
Posts: 12
Joined: 2 wk ago
Rep: +8
#7

hi!! new to the forum and to dependent types. this is amazing!! i'm coming from Haskell and I just have a basic question...

When you write J C d refl = d _ as the computation rule, what is the underscore _ representing? Shouldn't it be d x? But where does x come from?

Also does this whole thing work in Lean 4 or only Agda?

👍 Like (3) 💬 Quote ⚑ Report
😸
CubicalCat
★ Cubical Kitty
Tutorial Author
♦ Meow-derator
Posts: 2,847
Joined: 3 yrs ago
Rep: +4192
#8
PurrfectlyTyped wrote:
what is the underscore _ representing? Shouldn't it be d x?

Welcome to the forum, newcat! 🐾

The underscore _ is Agda's "I can infer this" syntax — it means "please figure out this argument automatically." In the definition:

J C d refl = d _

When we pattern-match on refl, Agda learns that the implicit arguments {x} {y} must both be the same variable (let's call it a), because refl : a ≡ a. So the _ in d _ is a — Agda can figure it out from context. You could also write d a but then you need to explicitly bind a in the pattern.

For your Lean 4 question: yes! Lean 4 has the same concept. It's called Eq.rec or you can use pattern matching on rfl:

-- Lean 4 equivalent:
theorem my_ap {A B : Type} (f : A  B)
    {x y : A} (h : x = y) : f x = f y :=
  match h with
  | rfl => rfl   -- pattern match on rfl, same idea as J!

Lean 4's match h with | rfl => ... is essentially pattern-matching-based J. The underlying elimination principle is the same. You can also use Eq.ndrec or congrArg from Mathlib. nya~

👍 Like (18) 💬 Quote ⚑ Report
😼
SchrödingerProof
Purrpositions-as-Types
Posts: 3,201
Joined: 4 yrs ago
Rep: +5,501
🏆 Veteran
#9

Pinning this thread. Genuinely one of the better introductions to J on the internet right now — the cat analogies make it approachable without sacrificing correctness.

One thing worth adding for completeness: there is a variant called substitution (or subst) which is based J written slightly differently — it's probably the form you'll encounter most often in Agda's stdlib:

-- subst is based J in a slightly more practical form
subst : {A : Set}
        (P : A  Set)
        {x y : A}
         x  y
         P x
         P y
subst P refl px = px   -- same as transport, different argument order

subst and transport are the same thing with arguments in different order. Agda's stdlib tends to use subst; HoTT literature tends to use transport. Don't let the naming inconsistency trip you up!

Also: CubicalCat, would you consider doing a follow-up on the groupoid laws for paths? Showing that paths form a groupoid (with refl, sym, trans) all provable from J would be a natural next step after this tutorial.

Great work as always, and the cat examples are, as usual, purrfect. 🐱

— SchrödingerProof | The proof is in superposition until you C-c C-l it
👍 Like (55) 💬 Quote 📌 Pinned by Mod ⚑ Report
Showing posts 1–9 of 9
⬆ Reply to thread

💬 Post a Reply