Zulip Chat Archive

Stream: new members

Topic: Confusion with `outParam`


Nick Attkiss (Nov 09 2024 at 17:00):

Hi all! In Section 7.2 the Mathematics in Lean tutorial introduces the outParam function to help fix typeclass resolution problems for monoid morphisms. In this setup, we start by representing objects that are "at least monoid morphisms":

class MonoidHomClass₁ (F : Type) (M N : Type) [Monoid M] [Monoid N] where
  toFun : F  M  N
  map_one :  f : F, toFun f 1 = 1
  map_mul :  f g g', toFun f (g * g') = toFun f g * toFun f g'

We then try to register the following CoeFun instance, which would raise an error if we actually used the instance keyword:

def badInst [Monoid M] [Monoid N] [MonoidHomClass₁ F M N] : CoeFun F (fun _  M  N) where
  coe := MonoidHomClass₁.toFun

To fix this, we change our definition of the MonoidHomClass₁ class by tagging M and N with outParam:

class MonoidHomClass₂ (F : Type) (M N : outParam Type) [Monoid M] [Monoid N] where
  toFun : F  M  N
  map_one :  f : F, toFun f 1 = 1
  map_mul :  f g g', toFun f (g * g') = toFun f g * toFun f g'

Now the CoeFun instance can be successfully registered:

instance [Monoid M] [Monoid N] [MonoidHomClass₂ F M N] : CoeFun F (fun _  M  N) where
  coe := MonoidHomClass₂.toFun

I don't see how adding outParam solves the error, or even what the error is exactly. As I understand it, the problem with the first implementation is that when Lean sees f x with f : F not a function type, it will try to use the CoeFun instance but get stuck because it doesn't know the order to infer F, M, and N. Marking M and N with outParam will tell Lean to infer F first, as it should.

But then why is the change made to the MonoidHomClass class, when the problem is with the CoeFun instance? And why does Lean raise an error when we define the bad CoeFun instance, instead of when we try to use the bad CoeFun instance?

I would really benefit from a step-by-step analysis of what Lean is trying to do at each line, in both the bad first implementation and the corrected second implementation; that way I can see exactly where the problem lies and how the change with outParam solves it. I've played around with set_option trace.Meta.synthInstance true to try to do this myself, but there's a lot of distracting irrelevant information.

Thanks for the help!

Daniel Weber (Nov 09 2024 at 17:21):

The problem is caused because docs#CoeFun has the function type as an outParam. This means that, when seeing f x for f : F Lean would search for some CoeFun F _ instance, but it can't apply badInst because synthesizing MonoidHomClass₁ requires knowing M and N, but it doesn't know them yet. It is able to figure this out when registering the instance and give an error.
When changing M, N in MonoidHomClass₂ to outParam it can now use the instance because it can search for some MonoidHomClass₂ F _ _, and once that resolves it knows M, N

Nick Attkiss (Nov 09 2024 at 18:19):

It's making a little more sense, but I'm still confused about what order everything happens in. An example could be useful. Sticking with the fixed implementation MonoidHomClass₂, let's define a structure for monoid morphisms and register it as an instance of MonoidHomClass₂:

structure MonoidHom₁ (G H : Type) [Monoid G] [Monoid H]  where
  toFun : G  H
  map_one : toFun 1 = 1
  map_mul :  g g', toFun (g * g') = toFun g * toFun g'

instance (M N : Type) [Monoid M] [Monoid N] : MonoidHomClass₂ (MonoidHom₁ M N) M N where
  toFun := MonoidHom₁.toFun
  map_one := fun f  f.map_one
  map_mul := fun f  f.map_mul

Now say we run the following check:

variable (G H : Type) [Monoid G] [Monoid H] (f : MonoidHom₁ G H) (x : G)

#check f x

The last line forces Lean to find a CoeFun instance. How does this play out from start to end? I.e., when do we pass from CoeFun to MonoidHomClass₂, how does Lean infer the variables and in what order, etc? Can we do a step-by-step analysis of the algorithm? I tried set_option trace.Meta.synthInstance true again but this time there's not enough information for me to make sense of it.


Last updated: May 02 2025 at 03:31 UTC