Zulip Chat Archive
Stream: lean4
Topic: question about monad type inference
Chris Lovett (Aug 18 2022 at 22:32):
Can someone explain how these 2 type instances interact exactly?
instance {ε : Type u} {m : Type u → Type v} [Monad m] : Monad (ExceptT ε m) where
pure := ExceptT.pure
bind := ExceptT.bind
map := ExceptT.map
instance : Monad (Except ε) where
pure := Except.pure
bind := Except.bind
map := Except.map
When I create ExceptT String Id
it must be triggering the first instance and when I use Except String
it must be triggering the second. But where my brain is exploding is when I realize the fact that logically in a type reduction kind of way ExceptT String Id
is equal to Except String
:
#reduce ExceptT String Id -- Except String α
So how does lean decide which of the above instances to use? Are there multiple phases of type inferencing or something, one that operates on non-reduced types and another that operates on reduced types, or does type inference never operate on reduced types? Or is the fact that I have to write the first instance above a kind of hack working around limitations in lean compiler? Or is the fact that I CAN write the first instance and redirect all the type inferencing to ExceptT.pure and ExceptT.bind and ExceptT.map a feature of Lean, it allows me to catch things before any kind of type resolution/reduction and change the game... ? How should I think about this...?
Mario Carneiro (Aug 18 2022 at 22:33):
One important thing that affects the answer to this question is whether ExceptT
and/or Except
is defined to be an abbrev
of the other
Chris Lovett (Aug 18 2022 at 22:37):
They are not, I'd assume abbreviations would be transparent to all this, and that I would not be able to create an instance that does one thing for the abbreviated type and something different for the abbreviation?
Mario Carneiro (Aug 18 2022 at 22:49):
Since they are not abbreviations, they act essentially as completely different types for the purpose of type class inference. If you infer the monad instance of an ExceptT
you get the ExceptT
instance and if you infer Except
you get the Except
instance
Chris Lovett (Aug 18 2022 at 22:51):
So this is because ExceptT is a def
instead of an abbrev
?
Mario Carneiro (Aug 18 2022 at 22:51):
If they were abbreviations, then the two type class instances would be overlapping, and you may get one or the other depending on which one is declared first / with higher priority, or perhaps only one applies in a particular case. Lean does not attempt to prevent overlapping instances from being declared, although they are generally a bad idea
Chris Lovett (Aug 18 2022 at 22:51):
So type inference happens before any type resolution (computation, reduction, elaboration? whatever the right term is)...
Mario Carneiro (Aug 18 2022 at 22:52):
for instance you could declare instance : Monad (Except String)
and that would get picked up in preference to the instance : Monad (Except ε)
if you declared it afterward
Chris Lovett (Aug 18 2022 at 22:52):
That helps - thanks.
Mario Carneiro (Aug 18 2022 at 22:52):
Chris Lovett said:
So type inference happens before any type resolution (computation, reduction, elaboration? whatever the right term is)...
No, everything is kind of interdependent
Mario Carneiro (Aug 18 2022 at 22:53):
it's all part of elaboration
Chris Lovett (Aug 18 2022 at 22:53):
I just tried abbreviations, and they work too ...
class MyMath (a : Type) where
plus : a -> a -> a
instance : MyMath Nat where
plus := Nat.add
def doubleIt [MyMath a] (x : a) : a :=
MyMath.plus x x
#eval doubleIt 5 - 10
abbrev Foo := MyMath
instance : Foo Nat where
plus := Nat.mul -- <<< redurect to multiply!
open MyMath
def fooIt [Foo a] (x : a) : a :=
plus x x
#eval fooIt 5 -- 25
Mario Carneiro (Aug 18 2022 at 22:53):
Note that reduction in the sense of #reduce
is not something that lean does at all during elaboration
Mario Carneiro (Aug 18 2022 at 22:54):
it will do some limited reduction when needed to fix type errors
Mario Carneiro (Aug 18 2022 at 22:54):
like if you have a function that accepts a ExceptT
and you give it Except
then it will unfold stuff to try to resolve that
Mario Carneiro (Aug 18 2022 at 22:55):
but inferType
generally doesn't unfold anything unless it is forced to, so the ExceptT
definition will stay fairly stable and typeclass inference is predictable on it
Chris Lovett (Aug 18 2022 at 22:56):
So unfold
is the term I was looking for thanks... In other languages I always think about type equivalence. Seems in Lean I have to forget about that and take Type expressions more literally...
Mario Carneiro (Aug 18 2022 at 22:56):
your example plus := Nat.mul
is showing lean doing unfolding to fix what would otherwise be a type error
Mario Carneiro (Aug 18 2022 at 22:56):
yeah, def
is not like a type alias
Mario Carneiro (Aug 18 2022 at 22:56):
abbrev
is closer to a type alias and notation
is closer still
Chris Lovett (Aug 18 2022 at 22:56):
Tell me more about why plus in my Foo instance would be a type error?
Mario Carneiro (Aug 18 2022 at 22:57):
ah, actually there isn't any unfolding needed there
Mario Carneiro (Aug 18 2022 at 22:58):
it does have to unfold Foo
to see why where plus := ...
is a proof of it
Mario Carneiro (Aug 18 2022 at 22:58):
that is, to uncover that Foo
is actually the class MyMath
with field plus
Chris Lovett (Aug 18 2022 at 22:58):
Sure. But you can see from my example that you can even specify more specific type instances on abbreviations! (so why did you ask about abbreviations earlier?)
Mario Carneiro (Aug 18 2022 at 23:00):
Here's a better example:
class MyMath (a : Type) where
plus : a -> a -> a
def Foo := Nat
instance : MyMath Nat where
plus := Nat.mul
open MyMath
def fooIt [MyMath a] (x : a) : a :=
plus x x
def five : Foo := (5 : Nat)
#eval fooIt 5 -- 25
#eval fooIt five -- failed
Mario Carneiro (Aug 18 2022 at 23:01):
If you change Foo
to an abbrev
then the second eval works
Mario Carneiro (Aug 18 2022 at 23:01):
and the definition of five
also doesn't need the type ascription
Chris Lovett (Aug 18 2022 at 23:02):
Very good, so can I conclude from your example that type inference does not unfold
defs.
Mario Carneiro (Aug 18 2022 at 23:04):
Here's another fun example:
class IsFoo (a : Type) where
isFoo : Bool
open IsFoo
def Foo := Nat
instance : IsFoo Nat := ⟨false⟩
instance : IsFoo Foo := ⟨true⟩
#eval isFoo Nat -- false
#eval isFoo Foo -- true
example : Nat = Foo := rfl
example : isFoo Nat ≠ isFoo Foo := by decide
Chris Lovett (Aug 18 2022 at 23:04):
Interesting I also cannot use a def in square brackets, this fails:
def Foo := MyMath
def fooIt [Foo a]... -- invalid binder annotation, type is not a class instance
Mario Carneiro (Aug 18 2022 at 23:06):
You can only use something which is a class
(up to unfolding reducible definitions) in instance brackets
Mario Carneiro (Aug 18 2022 at 23:08):
well, it's just a lint, you can turn it off if you want to but instances that don't have class
type can never be inferred
Chris Lovett (Aug 18 2022 at 23:11):
Ha, so this works, ok, thanks:
@[reducible]
def Foo := MyMath
So I wonder if the ExceptT instance exists mostly because def ExceptT
is not reducible... but there seems to be some other minor differences in it's implementation, using pure
more often... but otherwise looks very similar, almost like a shadow hierarchy. I wonder if monad transformers have this shadow instancing going on in general...
Mario Carneiro (Aug 18 2022 at 23:12):
yes, many def
s will inherit their instances from the underlying definition. Just search for uses of inferInstanceAs
Mario Carneiro (Aug 18 2022 at 23:13):
Not always though: in fact one of the reasons to use a def
on types is to equip a type with a non-standard instance
Mario Carneiro (Aug 18 2022 at 23:14):
for example mathlib has the with_top A
type that is a wrapper around option A
with an ordering that puts none
as the greatest element, and another with_bot A
type that is also option A
but puts none
at the bottom instead
Mario Carneiro (Aug 18 2022 at 23:14):
if those were abbrevs then it would be very confusing since you might get the with_top
order on with_bot
and vice versa
Mario Carneiro (Aug 18 2022 at 23:16):
In your example though I wouldn't expect ExceptT
and Except
to have the same monad instance: ExceptT
is wrapping another monad so it has to call the operations for that monad too
Chris Lovett (Aug 18 2022 at 23:16):
OOooh, yes I see now that abbrev is dangerous, it also changed my original #eval doubleIt 5 -- 25
!! So my second instance operating on Foo was not operating on Foo it was operating on MyMath, changing the behavior of the earlier instance! Wow yes this could lead to all kinds of trouble... and this is also the case with the @[reducible] def Foo
, so def here didn't help me.
Mario Carneiro (Aug 18 2022 at 23:19):
yes, abbrev
is just a shorthand for @[reducible, inline] def
IIRC
Mario Carneiro (Aug 18 2022 at 23:20):
the important thing is the reducibility setting: reducible
for abbrevs, semireducible
for regular defs and irreducible
for... @[irreducible] def
Mario Carneiro (Aug 18 2022 at 23:20):
irreducible
defs won't be unfolded by normal elaboration / unification, but you can explicitly unfold it by calling the unfold
tactic
Mario Carneiro (Aug 18 2022 at 23:21):
and then opaque
is the most irreducible: even the kernel doesn't think that the opaque definition unfolds, only the compiler will use the equation when generating code
Chris Lovett (Aug 18 2022 at 23:24):
So back to my earlier question, even though this is true:
example : ExceptT α Id = Except α :=
by simp[ExceptT, Except, Id] -- Goals accomplished 🎉
it has no bearing on how type inference works, since type inference doesn't care about type equivalence...
Mario Carneiro (Aug 18 2022 at 23:25):
it cares about definitional equality up to unfolding reducibles
Mario Carneiro (Aug 18 2022 at 23:25):
which is the "weakest" kind of unfolding, which you will see in most things that want to be mostly syntactic
Mario Carneiro (Aug 18 2022 at 23:26):
rw
and simp
match up to reducible defs too
Chris Lovett (Aug 18 2022 at 23:26):
Thanks, this info seems to be missing from: https://leanprover.github.io/theorem_proving_in_lean4/type_classes.html
Mario Carneiro (Aug 18 2022 at 23:28):
Indeed, abbrev
isn't mentioned in the book at all. This is reasonable considering that it didn't exist in lean 3 when TPIL was first written, and it was merely syntax-upgraded to lean 4 IIUC
Last updated: Dec 20 2023 at 11:08 UTC