19.1. Laws🔗
Having map, pure, seq, and bind operators with the appropriate types is not really sufficient to have a functor, applicative functor, or monad.
These operators must additionally satisfy certain axioms, which are often called the laws of the type class.
For a functor, the map operation must preserve identity and function composition. In other words, given a purported Functor f, for all x:f α:
Instances that violate these assumptions can be very surprising!
Additionally, because Functor includes mapConst to enable instances to provide a more efficient implementation, a lawful functor's mapConst should be equivalent to its default implementation.
The Lean standard library does not require proofs of these properties in every instance of Functor.
Nonetheless, if an instance violates them, then it should be considered a bug.
When proofs of these properties are necessary, an instance implicit parameter of type LawfulFunctor f can be used.
The LawfulFunctor class includes the necessary proofs.
🔗type classA functor satisfies the functor laws.
The Functor class contains the operations of a functor, but does not require that instances
prove they satisfy the laws of a functor. A LawfulFunctor instance includes proofs that the laws
are satisfied. Because Functor instances may provide optimized implementations of mapConst,
LawfulFunctor instances must also prove that the optimized implementation is equivalent to the
standard implementation.
Methods
id_map : ∀ {α : Type u} (x : f α), id <$> x = xThe map implementation preserves identity.
comp_map : ∀ {α β γ : Type u} (g : α → β) (h : β → γ) (x : f α), (h ∘ g) <$> x = h <$> g <$> xThe map implementation preserves function composition.
In addition to proving that the potentially-optimized SeqLeft.seqLeft and SeqRight.seqRight operations are equivalent to their default implementations, Applicative functors f must satisfy four laws.
🔗type classAn applicative functor satisfies the laws of an applicative functor.
The Applicative class contains the operations of an applicative functor, but does not require that
instances prove they satisfy the laws of an applicative functor. A LawfulApplicative instance
includes proofs that the laws are satisfied.
Because Applicative instances may provide optimized implementations of seqLeft and seqRight,
LawfulApplicative instances must also prove that the optimized implementation is equivalent to the
standard implementation.
Methods
id_map : ∀ {α : Type u} (x : f α), id <$> x = xcomp_map : ∀ {α β γ : Type u} (g : α → β) (h : β → γ) (x : f α), (h ∘ g) <$> x = h <$> g <$> xseqLeft_eq : ∀ {α β : Type u} (x : f α) (y : f β), x <* y = Function.const β <$> x <*> yseqLeft is equivalent to the default implementation.
seqRight_eq : ∀ {α β : Type u} (x : f α) (y : f β), x *> y = Function.const α id <$> x <*> yseqRight is equivalent to the default implementation.
pure_seq : ∀ {α β : Type u} (g : α → β) (x : f α), pure g <*> x = g <$> xpure before seq is equivalent to Functor.map.
This means that pure really is pure when occurring immediately prior to seq.
map_pure : ∀ {α β : Type u} (g : α → β) (x : α), g <$> pure x = pure (g x)Mapping a function over the result of pure is equivalent to applying the function under pure.
This means that pure really is pure with respect to Functor.map.
seq_pure : ∀ {α β : Type u} (g : f (α → β)) (x : α), g <*> pure x = (fun h => h x) <$> gpure after seq is equivalent to Functor.map.
This means that pure really is pure when occurring just after seq.
seq_assoc : ∀ {α β γ : Type u} (x : f α) (g : f (α → β)) (h : f (β → γ)), h <*> (g <*> x) = Function.comp <$> h <*> g <*> xseq is associative.
Changing the nesting of seq calls while maintaining the order of computations results in an
equivalent computation. This means that seq is not doing any more than sequencing.
The monad laws specify that pure followed by bind should be equivalent to function application (that is, pure has no effects), that bind followed by pure around a function application is equivalent to map, and that bind is associative.
🔗type classLawful monads are those that satisfy a certain behavioral specification. While all instances of
Monad should satisfy these laws, not all implementations are required to prove this.
LawfulMonad.mk' is an alternative constructor that contains useful defaults for many fields.
Methods
id_map : ∀ {α : Type u} (x : m α), id <$> x = xcomp_map : ∀ {α β γ : Type u} (g : α → β) (h : β → γ) (x : m α), (h ∘ g) <$> x = h <$> g <$> xseqRight_eq : ∀ {α β : Type u} (x : m α) (y : m β), x *> y = Function.const α id <$> x <*> ypure_seq : ∀ {α β : Type u} (g : α → β) (x : m α), pure g <*> x = g <$> xmap_pure : ∀ {α β : Type u} (g : α → β) (x : α), g <$> pure x = pure (g x)seq_pure : ∀ {α β : Type u} (g : m (α → β)) (x : α), g <*> pure x = (fun h => h x) <$> gseq_assoc : ∀ {α β γ : Type u} (x : m α) (g : m (α → β)) (h : m (β → γ)), h <*> (g <*> x) = Function.comp <$> h <*> g <*> xbind_pure_comp : ∀ {α β : Type u} (f : α → β) (x : m α),
(do
let a ← x
pure (f a)) =
f <$> xA bind followed by pure composed with a function is equivalent to a functorial map.
This means that pure really is pure after a bind and has no effects.
bind_map : ∀ {α β : Type u} (f : m (α → β)) (x : m α),
(do
let x_1 ← f
x_1 <$> x) =
f <*> xpure_bind : ∀ {α β : Type u} (x : α) (f : α → m β), pure x >>= f = f xpure followed by bind is equivalent to function application.
This means that pure really is pure before a bind and has no effects.
bind_assoc : ∀ {α β γ : Type u} (x : m α) (f : α → m β) (g : β → m γ), x >>= f >>= g = x >>= fun x => f x >>= gbind is associative.
Changing the nesting of bind calls while maintaining the order of computations results in an
equivalent computation. This means that bind is not doing more than data-dependent sequencing.
🔗theoremLawfulMonad.mk'.{u, v} (m : Type u → Type v) [Monad m]
(id_map : ∀ {α : Type u} (x : m α), id <$> x = x)
(pure_bind :
∀ {α β : Type u} (x : α) (f : α → m β), pure x >>= f = f x)
(bind_assoc :
∀ {α β γ : Type u} (x : m α) (f : α → m β) (g : β → m γ),
x >>= f >>= g = x >>= fun x => f x >>= g)
(map_const :
∀ {α β : Type u} (x : α) (y : m β),
Functor.mapConst x y = Function.const β x <$> y := by
intros; rfl)
(seqLeft_eq :
∀ {α β : Type u} (x : m α) (y : m β),
x <* y = do
let a ← x
let _ ← y
pure a := by
intros; rfl)
(seqRight_eq :
∀ {α β : Type u} (x : m α) (y : m β),
x *> y = do
let _ ← x
y := by
intros; rfl)
(bind_pure_comp :
∀ {α β : Type u} (f : α → β) (x : m α),
(do
let y ← x
pure (f y)) =
f <$> x := by
intros; rfl)
(bind_map :
∀ {α β : Type u} (f : m (α → β)) (x : m α),
(do
let x_1 ← f
x_1 <$> x) =
f <*> x := by
intros; rfl) :
LawfulMonad m LawfulMonad.mk'.{u, v}
(m : Type u → Type v) [Monad m]
(id_map :
∀ {α : Type u} (x : m α),
id <$> x = x)
(pure_bind :
∀ {α β : Type u} (x : α)
(f : α → m β), pure x >>= f = f x)
(bind_assoc :
∀ {α β γ : Type u} (x : m α)
(f : α → m β) (g : β → m γ),
x >>= f >>= g =
x >>= fun x => f x >>= g)
(map_const :
∀ {α β : Type u} (x : α) (y : m β),
Functor.mapConst x y =
Function.const β x <$> y := by
intros; rfl)
(seqLeft_eq :
∀ {α β : Type u} (x : m α) (y : m β),
x <* y = do
let a ← x
let _ ← y
pure a := by
intros; rfl)
(seqRight_eq :
∀ {α β : Type u} (x : m α) (y : m β),
x *> y = do
let _ ← x
y := by
intros; rfl)
(bind_pure_comp :
∀ {α β : Type u} (f : α → β)
(x : m α),
(do
let y ← x
pure (f y)) =
f <$> x := by
intros; rfl)
(bind_map :
∀ {α β : Type u} (f : m (α → β))
(x : m α),
(do
let x_1 ← f
x_1 <$> x) =
f <*> x := by
intros; rfl) :
LawfulMonad m
An alternative constructor for LawfulMonad which has more
defaultable fields in the common case.