Zulip Chat Archive

Stream: general

Topic: Recursion Schemes and type universes


Solomon (Mar 04 2024 at 18:12):

Hi. As an exercise I'm trying to do some basic recursion schemes but immediately ran into problems with type universes:

inductive Mu (f : Type u  Type v)  where
  | mkMu : {α : Type u}  (f α  α)  Mu f

def cata {f : Type u  Type v} {α : Type u} : (f α  α)  (Mu f)  α :=
  fun alg (Mu.mkMu f) => f alg
error: application type mismatch
  f alg
argument
  alg
has type
  f α  α : Type (max u v)
but is expected to have type
  f α : Type v

I dont have much experience dealing with type universes. How would I go about fixing this code?

Timo Carlin-Burns (Mar 04 2024 at 18:27):

This error doesn't seem to have to do with type universes at all! Your code is doing something confusing where you have two variables named f. I'll rename one of them to g and replace all of the universe parameters with just Type so we can talk about this code more easily:

inductive Mu (f : Type  Type)  where
  | mkMu : {α : Type}  (f α  α)  Mu f

def cata {f : Type  Type} {α : Type} : (f α  α)  (Mu f)  α :=
  fun alg (Mu.mkMu g) => g alg
application type mismatch
  g alg
argument
  alg
has type
  f α → α : Type
but is expected to have type
  f α✝ : Type

Timo Carlin-Burns (Mar 04 2024 at 18:29):

The error is that the expression g alg doesn't make sense because alg is not the right type to be an argument to the function g

Solomon (Mar 04 2024 at 19:16):

Oh thats silly of me. I made a mistake in my definition of Mu. It should be:

inductive Mu (f : Type u  Type v)  where
  | mkMu : {α : Type u}  ((f α  α)  α)  Mu f

def cata {f : Type u  Type v} {α : Type u} [Functor f] : (f α  α)  Mu f  α :=
  fun alg (Mu.mkMu g) => g alg

Which gives me the following error:

error: application type mismatch
  g alg
argument
  alg
has type
  f α  α : Type (max u v)
but is expected to have type
  f α  α : Type (max v u)

Is this just an issue of Lean not knowing that max is commutative?

Timo Carlin-Burns (Mar 04 2024 at 19:27):

It's still not about universes :). The important difference here is between α and α✝. I'll explain where α✝ comes from.

When you write fun alg (Mu.mkMu g) => you're pattern matching on the constructor Mu.mkMu which takes three arguments, f, α and g:

#check Mu.mkMu
-- Mu.mkMu.{u, v} {f : Type u → Type v} {α : Type u} (g : (f α → α) → α) : Mu f

g is included in your pattern because it's the only explicit argument, but the first two, f and α are implicit. f is fully determined by the type Mu f, but this value of α that was passed to Mu.mkMu to construct a Mu f could be anything. Since you're not explicitly giving it a name, it enters your local context as an inaccessible variable α✝. You can see the full local context to verify this by replacing your code with an _

def cata {f : Type u  Type v} {α : Type u} [Functor f] : (f α  α)  Mu f  α :=
  fun alg (Mu.mkMu g) => _
-- context:
-- f: Type u → Type v
-- α: Type u
-- inst✝: Functor f
-- alg: f α → α
-- x✝: Mu f
-- α✝: Type u
-- g: (f α✝ → α✝) → α✝
-- ⊢ α

Solomon (Mar 05 2024 at 05:57):

@Timo Carlin-Burns Thanks for the explanation! It took me a while but I finally made it through this implementation and got it to evaluate:

inductive Mu (f : Type u  Type u) : Type (u + 1)  where
  | mkMu : ((α : Type u)  ((f α  α)  α))  Mu f

def cata {f : Type u  Type u} {α : Type u} : (f α  α)  Mu f  α :=
  fun alg (Mu.mkMu f) => f α alg

inductive ListF : (α : Type u)  (r : Type u)  Type u where
  | nilF : ListF α r
  | consF (head : α) (tail : r) : ListF α r

def nil (a : Type u) : Mu (ListF α) :=
  Mu.mkMu (λ _ alg => alg ListF.nilF)

def cons : α  Mu (ListF α)  Mu (ListF α) :=
  λ x (Mu.mkMu xs) => Mu.mkMu (λ t alg => alg (ListF.consF x (xs t alg)))

def ex : Mu (ListF Bool) :=
  cons True (cons False (cons True (nil Bool)))

def len_algebra : ListF α Nat  Nat
  | ListF.nilF => 0
  | ListF.consF _ r => 1 + r

def len : Mu (ListF α)  Nat :=
  fun xs => cata len_algebra xs

#eval len ex

This is a lot tricker then in Haskell but I learned a lot.

Solomon (Mar 05 2024 at 06:46):

I can do Nu and ana easily enough:

inductive Nu (f : Type u  Type u) : Type (u + 1) where
  | mkNu : (α : Type u)  (α  f α)  α  Nu f

def ana {f : Type u  Type u} {α : Type u} : (α  f α)  α  Nu f :=
  fun coalg x => Nu.mkNu α coalg x

def list_coalg : Nat  ListF Nat Nat
  | Nat.zero => ListF.nilF
  | Nat.succ i => ListF.consF i i

def list_builder : Nat  Nu (ListF Nat) :=
  fun n => ana list_coalg n

But then the problem becomes how can I fold Nu f into some other functor (such as List Nat for my list_builder example). Is it possible to write cata for Nu and ana for Mu in this setting? In haskell we take advantage of explicit recursion or a hylomorphism to do that.

And what about Fix? Is it possible to write Fix? I haven't made any progress on it.


Last updated: May 02 2025 at 03:31 UTC