Zulip Chat Archive

Stream: lean4

Topic: Functor.map instance with `Alternative`


Devon Tuma (Feb 14 2025 at 21:19):

When a type has both a Monad instance and an Alternative instance it seems that Lean opts to derive the map operation from Alternative rather than Monad, which aren't equal in general (although they are for e.g. OptionT). Basic example:

example {m : Type u  Type v} [Monad m] [Alternative m] [LawfulMonad m]
    {α β : Type u} (mx : m α) (f : α  β) :
    f <$> mx = do let x  mx; return f x :=
  -- fails iff Alternative is included above
  by rw [map_eq_bind_pure_comp, Function.comp_def]

This makes it hard to write generic lemmas about the behavior of failure if you want to define something like this:

class LawfulAlternative (m : Type u  Type v) [Alternative m] [Monad m] where
  failure_bind' {α β : Type u} (g : α  m β) : failure >>= g = failure

is there a way to tell Lean to opt for a different type-class path in the definition of these lemmas besides writing out @Functor.map m diff_inst α β f mx?

Devon Tuma (Feb 14 2025 at 21:29):

Sorry if this should be in the general channel instead

Devon Tuma (Feb 17 2025 at 22:17):

It seems like the easiest way to handle this would be to have a type-class for just the syntax that Alternative extends that just provides the failure definition:

class Failure (f : Type u  Type v) where
  failure : {α : Type u}  f α

export Failure (failure)

class Alternative' (f : Type u  Type v) extends Failure f, Applicative f where
  orElse : {α : Type u}  f α  (Unit  f α)  f α

however Alternative is defined in core Lean so I'm not sure how possible it is to change this. My current solution has been to instead define things in reverse:

class Failure (m : Type u  Type v) where
  fail {α : Type u} : m α

instance {m : Type u  Type v} [h : Alternative m] : Failure m where
  fail := h.failure

And to then write lemmas in terms of that so Lean can't "see" the extra Applicative instance from Alternative. However this means that there are both Failure.fail and failure floating around as definitions. Is there a better way I should handle this?

Eric Wieser (Feb 17 2025 at 22:18):

does class AlternativeMonad f extends Monad f, Alternative f work for you?

Devon Tuma (Feb 17 2025 at 22:29):

That does seem like a more elegant solution and solves my initial issue. Would this be something reasonable for me to PR to mathlib with instances for Option/OptionT m?

Devon Tuma (Feb 17 2025 at 22:30):

I'm also not sure which of these would be considered more idiomatic for enforcing laws on the type:

class AlternativeMonad₁ (m : Type u  Type v) extends Monad m, Alternative m

-- Extra typeclass for laws
class LawfulAlternative₁ (m : Type u  Type v) [AlternativeMonad₁ m] where
  failure_bind {α β : Type u} (g : α  m β) : failure >>= g = failure

-- Law included already
class AlternativeMonad₂ (m : Type u  Type v) extends Monad m, Alternative m where
  failure_bind {α β : Type u} (g : α  m β) : failure >>= g = failure

Eric Wieser (Feb 17 2025 at 22:50):

class LawfulAlternativeMonad (m : Type u  Type v) [AlternativeMonad m] where
  failure_bind {α β : Type u} (g : α  m β) : failure >>= g = failure

looks like the approach consistent with what we have elsewhere

Eric Wieser (Feb 17 2025 at 22:50):

Or perhaps

class LawfulAlternativeMonad (m : Type u  Type v) [AlternativeMonad m] extends LawfulMonad m where
  failure_bind {α β : Type u} (g : α  m β) : failure >>= g = failure

Devon Tuma (Feb 17 2025 at 22:52):

The second is then sufficient to prove related things about map and seq, which should hold in any real use case I think

Aaron Liu (Feb 17 2025 at 22:53):

docs#LawfulBEq, docs#LawfulFunctor, docs#LawfulApplicative, etc. all use a mixin typeclass that extends the previous lawful versions.

class AlternativeMonad (m : Type u  Type v) extends Monad m, Alternative m

-- Extra typeclass for laws
class LawfulAlternative (m : Type u  Type v) [AlternativeMonad m] extends LawfulMonad m where
  failure_bind {α β : Type u} (g : α  m β) : failure >>= g = failure
  orElse_failure {α : Type u} (a : m α) : (a <|> failure) = a
  failure_orElse {α : Type u} (b : m α) : (failure <|> b) = b
  -- etc.

Devon Tuma (Feb 27 2025 at 01:22):

Opened a PR #22349. I'm a bit unsure what should go in Mathlib.Control vs. Batteries.Control, seems that at least lemmas like OptionT.run_failureand OptionT.run_mapConst could go to batteries instead. I have a lot of other lemmas of that kind sitting around that I'd like to port somewhere.

Devon Tuma (Feb 27 2025 at 02:00):

@Quang Dao I guess you have at least moved the monad lifting laws to batteries. Were you planning to do that for most things like this?

Kim Morrison (Feb 27 2025 at 03:58):

Batteries seems better to me. I'm not sure that Mathlib cares about these things without further motivation. :-)

Quang Dao (Feb 27 2025 at 04:06):

It does seem that Devon's PR contains natural AlternativeMonad instances for Set, Finset, List, etc. So they interact with other parts of mathlib


Last updated: May 02 2025 at 03:31 UTC