5.5. Varieties of Monads
The IO
monad has many, many effects, and is used for writing programs that need to interact with the world.
It is described in its own section.
Programs that use IO
are essentially black boxes: they are typically not particularly amenable to verification.
Many algorithms are easiest to express with a much smaller set of effects.
These effects can often be simulated; for example, mutable state can be simulated by passing around a tuple that contains both the program's value and the state.
These simulated effects are easier to reason formally about, because they are defined using ordinary code rather than new language primitives.
The standard library provides abstractions for working with commonly-used effects.
Many frequently-used effects fall into a small number of categories:
- State monads have mutable state
Computations that have access to some data that may be modified by other parts of the computation use mutable state.
State can be implemented in a variety of ways, described in the section on state monads and captured in the MonadState
type class.
- Reader monads are parameterized computations
Computations that can read the value of some parameter provided by a context exist in most programming languages, but many languages that feature state and exceptions as first-class features do not have built-in facilities for defining new parameterized computations.
Typically, these computations are provided with a parameter value when invoked, and sometimes they can locally override it.
Parameter values have dynamic extent: the value provided most recently in the call stack is the one that is used.
They can be simulated by passing a value unchanged through a sequence of function calls; however, this technique can make code harder to read and introduces a risk that the values may be passed incorrectly to further calls by mistake.
They can also be simulated using mutable state with a careful discipline surrounding the modification of the state.
Monads that maintain a parameter, potentially allowing it to be overridden in a section of the call stack, are called reader monads.
Reader monads are captured in the MonadReader
type class.
Additionally, reader monads that allow the parameter value to be locally overridden are captured in the MonadWithReader
type class.
- Exception monads have exceptions
Computations that may terminate early with an exceptional value use exceptions.
They are typically modeled with a sum type that has a constructor for ordinary termination and a constructor for early termination with errors.
Exception monads are described in the section on exception monads, and captured in the MonadExcept
type class.
5.5.1. Monad Type Classes
Using type classes like MonadState
and MonadExcept
allow client code to be polymorphic with respect to monads.
Together with automatic lifting, this allows programs to be re-usable in many different monads and makes them more robust to refactoring.
It's important to be aware that effects in a monad may not interact in only one way.
For example, a monad with state and exceptions may or may not roll back state changes when an exception is thrown.
If this matters for the correctness of a function, then it should use a more specific signature.
Effect Ordering
The function sumNonFives
adds the contents of a list using a state monad, terminating early if it encounters a 5
.
def sumNonFives {m}
[Monad m] [MonadState Nat m] [MonadExcept String m]
(xs : List Nat) :
m Unit := do
for x in xs do
if x == 5 then
throw "Five was encountered"
else
modify (· + x)
Running it in one monad returns the state at the time that 5
was encountered:
(Except.error "Five was encountered", 10)
#eval
sumNonFives (m := ExceptT String (StateM Nat))
[1, 2, 3, 4, 5, 6] |>.run |>.run 0
In another, the state is discarded:
Except.error "Five was encountered"
#eval
sumNonFives (m := StateT Nat (Except String))
[1, 2, 3, 4, 5, 6] |>.run 0
In the second case, an exception handler would roll back the state to its value at the start of the Lean.Parser.Term.termTry : term
try
.
The following function is thus incorrect:
def sumUntilFive {m}
[Monad m] [MonadState Nat m] [MonadExcept String m]
(xs : List Nat) :
m Nat := do
MonadState.set 0
try
sumNonFives xs
catch _ =>
pure ()
get
In one monad, the answer is correct:
Except.ok 10
#eval
sumUntilFive (m := ExceptT String (StateM Nat))
[1, 2, 3, 4, 5, 6] |>.run |>.run' 0
In the other, it is not:
Except.ok 0
#eval
sumUntilFive (m := StateT Nat (Except String))
[1, 2, 3, 4, 5, 6] |>.run' 0
A single monad may support multiple version of the same effect.
For example, there might be a mutable Nat
and a mutable String
or two separate reader parameters.
As long as they have different types, it should be convenient to access both.
In typical use, some monadic operations that are overloaded in type classes have type information available for instance synthesis, while others do not.
For example, the argument passed to set
determines the type of the state to be used, while get
takes no such argument.
The type information present in applications of set
can be used to pick the correct instance when multiple states are available, which suggests that the type of the mutable state should be an input parameter or semi-output parameter so that it can be used to select instances.
The lack of type information present in uses of get
, on the other hand, suggests that the type of the mutable state should be an output parameter in MonadState
, so type class synthesis determines the state's type from the monad itself.
This dichotomy is solved by having two versions of many of the effect type classes.
The version with a semi-output parameter has the suffix -Of
, and its operations take types explicitly as needed.
Examples include MonadStateOf
, MonadReaderOf
, and MonadExceptOf
.
The operations with explicit type parameters have names ending in -The
, such as getThe
, readThe
, and tryCatchThe
.
The name of the version with an output parameter is undecorated.
The standard library exports a mix of operations from the -Of
and undecorated versions of each type class, based on what has good inference behavior in typical use cases.
State Types
The state monad M
has two separate states: a Nat
and a String
.
abbrev M := StateT Nat (StateM String)
Because get
is an alias for MonadState.get
, the state type is an output parameter.
This means that Lean selects a state type automatically, in this case the one from the outermost monad transformer:
get : M Nat
#check (get : M _)
Only the outermost may be used, because the type of the state is an output parameter.
#check (failed to synthesize
MonadState String M
Additional diagnostic information may be available using the `set_option diagnostics true` command.
get : M String)
failed to synthesize
MonadState String M
Additional diagnostic information may be available using the `set_option diagnostics true` command.
Providing the state type explicitly using getThe
from MonadStateOf
allows both states to be read.
(getThe String, getThe Nat) : M String × M Nat
#check ((getThe String, getThe Nat) : M String × M Nat)
Setting a state works for either type, because the state type is a semi-output parameter on MonadStateOf
.
set 4 : M PUnit
#check (set 4 : M Unit)
set "Four" : M PUnit
#check (set "Four" : M Unit)
A monad transformer is a function that, when provided with a monad, gives back a new monad.
Typically, this new monad has all the effects of the original monad along with some additional ones.
A monad transformer consists of the following:
-
A function T
that constructs the new monad's type from an existing monad
-
A run
function that adapts a T m α
into some variant of m
, often requiring additional parameters and returning a more specific type under m
-
An instance of [Monad m] → Monad (T m)
that allows the transformed monad to be used as a monad
-
An instance of MonadLift
that allows the original monad's code to be used in the transformed monad
-
If possible, an instance of MonadControl m (T m)
that allows actions from the transformed monad to be used in the original monad
Typically, a monad transformer also provides instances of one or more type classes that describe the effects that it introduces.
The transformer's Monad
and MonadLift
instances make it practical to write code in the transformed monad, while the type class instances allow the transformed monad to be used with polymorphic functions.
The Identity Monad Transformer
The identity monad transformer neither adds nor removes capabilities to the transformed monad.
Its definition is the identity function, suitably specialized:
def IdT (m : Type u → Type v) : Type u → Type v := m
Similarly, the run
function requires no additional arguments and just returns an m α
:
def IdT.run (act : IdT m α) : m α := act
The monad instance relies on the monad instance for the transformed monad, selecting it via type ascriptions:
instance [Monad m] : Monad (IdT m) where
pure x := (pure x : m _)
bind x f := (x >>= f : m _)
Because IdT m
is definitionally equal to m
, the MonadLift m (IdT m)
instance doesn't need to modify the action being lifted:
instance : MonadLift m (IdT m) where
monadLift x := x
The MonadControl
instance is similarly simple.
instance [Monad m] : MonadControl m (IdT m) where
stM α := α
liftWith f := f (fun x => Id.run <| pure x)
restoreM v := v
The Lean standard library provides transformer versions of many different monads, including ReaderT
, ExceptT
, and StateT
, along with variants using other representations such as StateCpsT
, StateRefT
, and ExceptCpsT
.
Additionally, the EStateM
monad is equivalent to combining ExceptT
and StateT
, but it can use a more specialized representation to improve performance.
5.5.3. Identity
The identity monad Id
has no effects whatsoever.
Both Id
and the corresponding implementation of pure
are the identity function, and bind
is reversed function application.
The identity monad has two primary use cases:
-
It can be the type of a Lean.Parser.Term.do : term
do
block that implements a pure function with local effects.
-
It can be placed at the bottom of a stack of monad transformers.
🔗defId.{u} (type : Type u) : Type u
🔗defId.run.{u_1} {α : Type u_1} (x : Id α) : α
Local Effects with the Identity Monad
This code block implements a countdown procedure by using simulated local mutability in the identity monad.
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
#eval Id.run do
let mut xs := []
for x in [0:10] do
xs := x :: xs
pure xs
5.5.4. State🔗
State monads provide access to a mutable value.
The underlying implementation may a tuple to simulate mutability, or it may use something like ST.Ref
to ensure mutation.
Even those implementations that use a tuple may in fact use mutation at run-time due to Lean's use of mutation when there are unique references to values, but this requires a programming style that prefers modify
and modifyGet
over get
and set
.
5.5.4.1. General State API
🔗type classMonadState.{u, v} (σ : outParam (Type u))
(m : Type u → Type v) : Type (max (u + 1) v)
Similar to MonadStateOf
, but σ
is an outParam
for convenience.
Instance Constructor
Methods
get | : | m σ |
|
(← get) : σ gets the state out of a monad m .
|
set | : | σ → m PUnit |
|
set (s : σ) replaces the state with value s .
|
modifyGet | : | {α : Type u} → (σ → α × σ) → m α |
|
modifyGet (f : σ → α × σ) applies f to the current state, replaces
the state with the return value, and returns a computed value.
It is equivalent to do let (a, s) := f (← get); put s; pure a , but
modifyGet f may be preferable because the former does not use the state
linearly (without sufficient inlining).
|
🔗defMonadState.get.{u, v} {σ : outParam (Type u)}
{m : Type u → Type v}
[self : MonadState σ m] : m σ
(← get) : σ
gets the state out of a monad m
.
🔗defmodify.{u, v} {σ : Type u} {m : Type u → Type v}
[MonadState σ m] (f : σ → σ) : m PUnit
modify (f : σ → σ)
applies the function f
to the state.
It is equivalent to do set (f (← get))
, but modify f
may be preferable
because the former does not use the state linearly (without sufficient inlining).
🔗defMonadState.modifyGet.{u, v}
{σ : outParam (Type u)} {m : Type u → Type v}
[self : MonadState σ m] {α : Type u} :
(σ → α × σ) → m α
modifyGet (f : σ → α × σ)
applies f
to the current state, replaces
the state with the return value, and returns a computed value.
It is equivalent to do let (a, s) := f (← get); put s; pure a
, but
modifyGet f
may be preferable because the former does not use the state
linearly (without sufficient inlining).
🔗defgetModify.{u, v} {σ : Type u}
{m : Type u → Type v} [MonadState σ m]
(f : σ → σ) : m σ
getModify f
gets the state, applies function f
, and returns the old value
of the state. It is equivalent to get <* modify f
but may be more efficient.
🔗type classMonadStateOf.{u, v} (σ : semiOutParam (Type u))
(m : Type u → Type v) : Type (max (u + 1) v)
An implementation of MonadState
. In contrast to the Haskell implementation,
we use overlapping instances to derive instances automatically from monadLift
.
Instance Constructor
Methods
get | : | m σ |
|
(← get) : σ gets the state out of a monad m .
|
set | : | σ → m PUnit |
|
set (s : σ) replaces the state with value s .
|
modifyGet | : | {α : Type u} → (σ → α × σ) → m α |
|
modifyGet (f : σ → α × σ) applies f to the current state, replaces
the state with the return value, and returns a computed value.
It is equivalent to do let (a, s) := f (← get); put s; pure a , but
modifyGet f may be preferable because the former does not use the state
linearly (without sufficient inlining).
|
🔗defgetThe.{u, v} (σ : Type u) {m : Type u → Type v}
[MonadStateOf σ m] : m σ
Like get
, but with σ
explicit. This is useful if a monad supports
MonadStateOf
for multiple different types σ
.
🔗defmodifyThe.{u, v} (σ : Type u)
{m : Type u → Type v} [MonadStateOf σ m]
(f : σ → σ) : m PUnit
Like modify
, but with σ
explicit. This is useful if a monad supports
MonadStateOf
for multiple different types σ
.
🔗defmodifyGetThe.{u, v} {α : Type u} (σ : Type u)
{m : Type u → Type v} [MonadStateOf σ m]
(f : σ → α × σ) : m α
Like modifyGet
, but with σ
explicit. This is useful if a monad supports
MonadStateOf
for multiple different types σ
.
5.5.4.2. Tuple-Based State Monads
The tuple-based state monads represent a computation with states that have type σ
yielding values of type α
as functions that take a starting state and yield a value paired with a final state, e.g. σ → α × σ
.
The Monad
operations thread the state correctly through the computation.
🔗defStateM.{u} (σ α : Type u) : Type u
🔗defStateT.{u, v} (σ : Type u) (m : Type u → Type v)
(α : Type u) : Type (max u v)
🔗defStateT.run.{u, v} {σ : Type u}
{m : Type u → Type v} {α : Type u}
(x : StateT σ m α) (s : σ) : m (α × σ)
🔗defStateT.get.{u, v} {σ : Type u}
{m : Type u → Type v} [Monad m]
: StateT σ m σ
🔗defStateT.set.{u, v} {σ : Type u}
{m : Type u → Type v} [Monad m]
: σ → StateT σ m PUnit
🔗defStateT.failure.{u, v} {σ : Type u}
{m : Type u → Type v} [Alternative m]
{α : Type u} : StateT σ m α
🔗defStateT.run'.{u, v} {σ : Type u}
{m : Type u → Type v} [Functor m]
{α : Type u} (x : StateT σ m α) (s : σ)
: m α
🔗defStateT.bind.{u, v} {σ : Type u}
{m : Type u → Type v} [Monad m]
{α β : Type u} (x : StateT σ m α)
(f : α → StateT σ m β) : StateT σ m β
🔗defStateT.modifyGet.{u, v} {σ : Type u}
{m : Type u → Type v} [Monad m] {α : Type u}
(f : σ → α × σ) : StateT σ m α
🔗defStateT.lift.{u, v} {σ : Type u}
{m : Type u → Type v} [Monad m] {α : Type u}
(t : m α) : StateT σ m α
🔗defStateT.map.{u, v} {σ : Type u}
{m : Type u → Type v} [Monad m]
{α β : Type u} (f : α → β)
(x : StateT σ m α) : StateT σ m β
🔗defStateT.pure.{u, v} {σ : Type u}
{m : Type u → Type v} [Monad m] {α : Type u}
(a : α) : StateT σ m α
5.5.4.3. State Monads in Continuation Passing Style
Continuation-passing-style state monads represent stateful computations as functions that, for any type whatsoever, take an initial state and a continuation (modeled as a function) that accepts a value and an updated state.
An example of such a type is (δ : Type u) → σ → (α → σ → δ) → δ
, though StateCpsT
is a transformer that can be applied to any monad.
State monads in continuation passing style have different performance characteristics than tuple-based state monads; for some applications, it may be worth benchmarking them.
🔗defStateCpsT.{u, v} (σ : Type u)
(m : Type u → Type v) (α : Type u)
: Type (max (u + 1) v)
🔗defStateCpsT.lift.{u, v} {α σ : Type u}
{m : Type u → Type v} [Monad m] (x : m α)
: StateCpsT σ m α
🔗defStateCpsT.runK.{u, v} {α σ : Type u}
{m : Type u → Type v} {β : Type u}
(x : StateCpsT σ m α) (s : σ)
(k : α → σ → m β) : m β
🔗defStateCpsT.run'.{u, v} {α σ : Type u}
{m : Type u → Type v} [Monad m]
(x : StateCpsT σ m α) (s : σ) : m α
🔗defStateCpsT.run.{u, v} {α σ : Type u}
{m : Type u → Type v} [Monad m]
(x : StateCpsT σ m α) (s : σ) : m (α × σ)
5.5.4.4. State Monads from Mutable References
The monad StateRefT σ m
is a specialized state monad transformer that can be used when m
is a monad to which ST
computations can be lifted.
It implements the operations of MonadState
using an ST.Ref
, rather than pure functions.
This ensures that mutation is actually used at run time.
ST
and EST
require a phantom type parameter that's used together with runST
's polymorphic function argument to encapsulate mutability.
Rather than require this as a parameter to the transformer, an auxiliary type class STWorld
is used to propagate it directly from m
.
The transformer itself is defined as a syntax extension and an elaborator, rather than an ordinary function.
This is because STWorld
has no methods: it exists only to propagate information from the inner monad to the transformed monad.
Nonetheless, its instances are terms; keeping them around could lead to unnecessarily large types.
🔗type classSTWorld (σ : outParam Type) (m : Type → Type)
: Type
Instance Constructor
Methods
syntax
The syntax for StateRefT σ m
accepts two arguments:
term ::= ...
| StateRefT term (macroDollarArg
| term)
Its elaborator synthesizes an instance of STWorld ω m
to ensure that m
supports mutable references.
Having discovered the value of ω
, it then produces the term StateRefT' ω σ m
, discarding the synthesized instance.
🔗defStateRefT' (ω σ : Type) (m : Type → Type)
(α : Type) : Type
🔗defStateRefT'.modifyGet {ω σ : Type}
{m : Type → Type} {α : Type}
[MonadLiftT (ST ω) m] (f : σ → α × σ)
: StateRefT' ω σ m α
🔗defStateRefT'.lift {ω σ : Type} {m : Type → Type}
{α : Type} (x : m α) : StateRefT' ω σ m α
5.5.5. Reader
🔗type classMonadReader.{u, v} (ρ : outParam (Type u))
(m : Type u → Type v) : Type v
Similar to MonadReaderOf
, but ρ
is an outParam
for convenience.
Instance Constructor
Methods
read | : | m ρ |
|
(← read) : ρ reads the state out of monad m .
|
🔗type classMonadReaderOf.{u, v} (ρ : semiOutParam (Type u))
(m : Type u → Type v) : Type v
An implementation of Haskell's MonadReader
(sans functional dependency; see also MonadReader
in this module). It does not contain local
because this
function cannot be lifted using monadLift
. local
is instead provided by
the MonadWithReader
class as withReader
.
Note: This class can be seen as a simplification of the more "principled" definition
class MonadReaderOf (ρ : Type u) (n : Type u → Type u) where
lift {α : Type u} : ({m : Type u → Type u} → [Monad m] → ReaderT ρ m α) → n α
Instance Constructor
Methods
read | : | m ρ |
|
(← read) : ρ reads the state out of monad m .
|
🔗defreadThe.{u, v} (ρ : Type u)
{m : Type u → Type v} [MonadReaderOf ρ m]
: m ρ
Like read
, but with ρ
explicit. This is useful if a monad supports
MonadReaderOf
for multiple different types ρ
.
🔗type classMonadWithReader.{u, v} (ρ : outParam (Type u))
(m : Type u → Type v) : Type (max (u + 1) v)
Similar to MonadWithReaderOf
, but ρ
is an outParam
for convenience.
Instance Constructor
Methods
withReader | : | {α : Type u} → (ρ → ρ) → m α → m α |
|
withReader (f : ρ → ρ) (x : m α) : m α runs the inner x : m α inside
a modified context after applying the function f : ρ → ρ .
|
🔗type classMonadWithReaderOf.{u, v}
(ρ : semiOutParam (Type u))
(m : Type u → Type v) : Type (max (u + 1) v)
MonadWithReaderOf ρ
adds the operation withReader : (ρ → ρ) → m α → m α
.
This runs the inner x : m α
inside a modified context after applying the
function f : ρ → ρ
. In addition to ReaderT
itself, this operation lifts
over most monad transformers, so it allows us to apply withReader
to monads
deeper in the stack.
Instance Constructor
Methods
withReader | : | {α : Type u} → (ρ → ρ) → m α → m α |
|
withReader (f : ρ → ρ) (x : m α) : m α runs the inner x : m α inside
a modified context after applying the function f : ρ → ρ .
|
🔗defwithTheReader.{u, v} (ρ : Type u)
{m : Type u → Type v}
[MonadWithReaderOf ρ m] {α : Type u}
(f : ρ → ρ) (x : m α) : m α
Like withReader
, but with ρ
explicit. This is useful if a monad supports
MonadWithReaderOf
for multiple different types ρ
.
🔗defReaderT.{u, v} (ρ : Type u)
(m : Type u → Type v) (α : Type u)
: Type (max u v)
An implementation of Haskell's ReaderT
. This is a monad transformer which
equips a monad with additional read-only state, of type ρ
.
🔗defReaderM.{u} (ρ α : Type u) : Type u
🔗defReaderT.adapt.{u, v} {ρ : Type u}
{m : Type u → Type v} {ρ' α : Type u}
(f : ρ' → ρ)
: ReaderT ρ m α → ReaderT ρ' m α
adapt (f : ρ' → ρ)
precomposes function f
on the reader state of a
ReaderT ρ
, yielding a ReaderT ρ'
.
🔗defReaderT.read.{u, v} {ρ : Type u}
{m : Type u → Type v} [Monad m]
: ReaderT ρ m ρ
(← read) : ρ
gets the read-only state of a ReaderT ρ
.
🔗defReaderT.run.{u, v} {ρ : Type u}
{m : Type u → Type v} {α : Type u}
(x : ReaderT ρ m α) (r : ρ) : m α
If x : ReaderT ρ m α
and r : ρ
, then x.run r : ρ
runs the monad with the
given reader state.
🔗defReaderT.bind.{u, v} {ρ : Type u}
{m : Type u → Type v} [Monad m]
{α β : Type u} (x : ReaderT ρ m α)
(f : α → ReaderT ρ m β) : ReaderT ρ m β
The bind
operation of the ReaderT
monad.
🔗defReaderT.failure.{u_1, u_2}
{m : Type u_1 → Type u_2} {ρ α : Type u_1}
[Alternative m] : ReaderT ρ m α
🔗defReaderT.pure.{u, v} {ρ : Type u}
{m : Type u → Type v} [Monad m] {α : Type u}
(a : α) : ReaderT ρ m α
The pure
operation of the ReaderT
monad.
5.5.6. Option🔗
Ordinarily, Option
is thought of as data, similarly to a nullable type.
It can also be considered as a monad, and thus a way of performing computations.
The Option
monad and its transformer OptionT
can be understood as describing computations that may terminate early, discarding the results.
Callers can check for early termination and invoke a fallback if desired using OrElse.orElse
or by treating it as a MonadExcept Unit
.
🔗defOptionT.{u, v} (m : Type u → Type v)
(α : Type u) : Type v
🔗defOptionT.run.{u, v} {m : Type u → Type v}
{α : Type u} (x : OptionT m α)
: m (Option α)
🔗defOptionT.lift.{u, v} {m : Type u → Type v}
[Monad m] {α : Type u} (x : m α)
: OptionT m α
🔗defOptionT.mk.{u, v} {m : Type u → Type v}
{α : Type u} (x : m (Option α))
: OptionT m α
🔗defOptionT.pure.{u, v} {m : Type u → Type v}
[Monad m] {α : Type u} (a : α) : OptionT m α
🔗defOptionT.fail.{u, v} {m : Type u → Type v}
[Monad m] {α : Type u} : OptionT m α
5.5.7. Exceptions🔗
Exception monads describe computations that terminate early (fail).
Failing computations provide their caller with an exception value that describes why they failed.
In other words, computations either return a value or an exception.
The inductive type Except
captures this pattern, and is itself a monad.
5.5.7.1. Exceptions
🔗inductive typeExcept.{u, v} (ε : Type u) (α : Type v)
: Type (max u v)
Except ε α
is a type which represents either an error of type ε
, or an "ok"
value of type α
. The error type is listed first because
Except ε : Type → Type
is a Monad
: the pure operation is ok
and the bind
operation returns the first encountered error
.
Constructors
-
error.{u, v} {ε : Type u} {α : Type v}
: ε → Except ε α
A failure value of type ε
-
ok.{u, v} {ε : Type u} {α : Type v}
: α → Except ε α
A success value of type α
🔗defExcept.pure.{u, u_1} {ε : Type u} {α : Type u_1}
(a : α) : Except ε α
🔗defExcept.bind.{u, u_1, u_2} {ε : Type u}
{α : Type u_1} {β : Type u_2}
(ma : Except ε α) (f : α → Except ε β)
: Except ε β
🔗defExcept.map.{u, u_1, u_2} {ε : Type u}
{α : Type u_1} {β : Type u_2} (f : α → β)
: Except ε α → Except ε β
🔗defExcept.mapError.{u, u_1, u_2} {ε : Type u}
{ε' : Type u_1} {α : Type u_2} (f : ε → ε')
: Except ε α → Except ε' α
🔗defExcept.tryCatch.{u, u_1} {ε : Type u}
{α : Type u_1} (ma : Except ε α)
(handle : ε → Except ε α) : Except ε α
🔗defExcept.isOk.{u, u_1} {ε : Type u} {α : Type u_1}
: Except ε α → Bool
🔗defExcept.toOption.{u, u_1} {ε : Type u}
{α : Type u_1} : Except ε α → Option α
🔗defExcept.toBool.{u, u_1} {ε : Type u}
{α : Type u_1} : Except ε α → Bool
Returns true if the value is Except.ok
, false otherwise.
5.5.7.2. Type Class
🔗type classMonadExcept.{u, v, w} (ε : outParam (Type u))
(m : Type v → Type w)
: Type (max (max u (v + 1)) w)
Similar to MonadExceptOf
, but ε
is an outParam
for convenience.
Instance Constructor
Methods
throw | : | {α : Type v} → ε → m α |
|
throw : ε → m α "throws an error" of type ε to the nearest enclosing
catch block.
|
tryCatch | : | {α : Type v} → m α → (ε → m α) → m α |
|
tryCatch (body : m α) (handler : ε → m α) : m α will catch any errors in
body and pass the resulting error to handler .
Errors in handler will not be caught.
|
🔗defMonadExcept.ofExcept.{u_1, u_2, u_3}
{m : Type u_1 → Type u_2} {ε : Type u_3}
{α : Type u_1} [Monad m] [MonadExcept ε m]
: Except ε α → m α
"Unwraps" an Except ε α
to get the α
, or throws the exception otherwise.
🔗defMonadExcept.orElse.{u, v, w} {ε : Type u}
{m : Type v → Type w} [MonadExcept ε m]
{α : Type v} (t₁ : m α) (t₂ : Unit → m α)
: m α
A MonadExcept
can implement t₁ <|> t₂
as try t₁ catch _ => t₂
.
🔗defMonadExcept.orelse'.{u, v, w} {ε : Type u}
{m : Type v → Type w} [MonadExcept ε m]
{α : Type v} (t₁ t₂ : m α)
(useFirstEx : Bool := true) : m α
Alternative orelse operator that allows to select which exception should be used.
The default is to use the first exception since the standard orelse
uses the second.
🔗type classMonadExceptOf.{u, v, w}
(ε : semiOutParam (Type u))
(m : Type v → Type w)
: Type (max (max u (v + 1)) w)
An implementation of Haskell's MonadError
class. A MonadError ε m
is a
monad m
with two operations:
-
throw : ε → m α
"throws an error" of type ε
to the nearest enclosing
catch block
-
tryCatch (body : m α) (handler : ε → m α) : m α
will catch any errors in
body
and pass the resulting error to handler
.
Errors in handler
will not be caught.
The try ... catch e => ...
syntax inside do
blocks is sugar for the
tryCatch
operation.
Instance Constructor
Methods
throw | : | {α : Type v} → ε → m α |
|
throw : ε → m α "throws an error" of type ε to the nearest enclosing
catch block.
|
tryCatch | : | {α : Type v} → m α → (ε → m α) → m α |
|
tryCatch (body : m α) (handler : ε → m α) : m α will catch any errors in
body and pass the resulting error to handler .
Errors in handler will not be caught.
|
🔗defthrowThe.{u, v, w} (ε : Type u)
{m : Type v → Type w} [MonadExceptOf ε m]
{α : Type v} (e : ε) : m α
This is the same as throw
, but allows specifying the particular error type
in case the monad supports throwing more than one type of error.
🔗deftryCatchThe.{u, v, w} (ε : Type u)
{m : Type v → Type w} [MonadExceptOf ε m]
{α : Type v} (x : m α) (handle : ε → m α)
: m α
This is the same as tryCatch
, but allows specifying the particular error type
in case the monad supports throwing more than one type of error.
5.5.7.3. “Finally” Computations
🔗type classMonadFinally.{u, v} (m : Type u → Type v)
: Type (max (u + 1) v)
Instance Constructor
Methods
tryFinally' | : | {α β : Type u} → m α → (Option α → m β) → m (α × β) |
|
tryFinally' x f runs x and then the "finally" computation f .
When x succeeds with a : α , f (some a) is returned. If x fails
for m 's definition of failure, f none is returned. Hence tryFinally'
can be thought of as performing the same role as a finally block in
an imperative programming language.
|
🔗defExceptT.{u, v} (ε : Type u)
(m : Type u → Type v) (α : Type u) : Type v
🔗defExceptT.lift.{u, v} {ε : Type u}
{m : Type u → Type v} [Monad m] {α : Type u}
(t : m α) : ExceptT ε m α
🔗defExceptT.run.{u, v} {ε : Type u}
{m : Type u → Type v} {α : Type u}
(x : ExceptT ε m α) : m (Except ε α)
🔗defExceptT.pure.{u, v} {ε : Type u}
{m : Type u → Type v} [Monad m] {α : Type u}
(a : α) : ExceptT ε m α
🔗defExceptT.bind.{u, v} {ε : Type u}
{m : Type u → Type v} [Monad m]
{α β : Type u} (ma : ExceptT ε m α)
(f : α → ExceptT ε m β) : ExceptT ε m β
🔗defExceptT.bindCont.{u, v} {ε : Type u}
{m : Type u → Type v} [Monad m]
{α β : Type u} (f : α → ExceptT ε m β)
: Except ε α → m (Except ε β)
🔗defExceptT.tryCatch.{u, v} {ε : Type u}
{m : Type u → Type v} [Monad m] {α : Type u}
(ma : ExceptT ε m α)
(handle : ε → ExceptT ε m α) : ExceptT ε m α
🔗defExceptT.mk.{u, v} {ε : Type u}
{m : Type u → Type v} {α : Type u}
(x : m (Except ε α)) : ExceptT ε m α
🔗defExceptT.map.{u, v} {ε : Type u}
{m : Type u → Type v} [Monad m]
{α β : Type u} (f : α → β)
(x : ExceptT ε m α) : ExceptT ε m β
🔗defExceptT.adapt.{u, v} {ε : Type u}
{m : Type u → Type v} [Monad m]
{ε' α : Type u} (f : ε → ε')
: ExceptT ε m α → ExceptT ε' m α
5.5.7.5. Exception Monads in Continuation Passing Style
Continuation-passing-style exception monads represent potentially-failing computations as functions that take success and failure continuations, both of which return the same type, returning that type.
They must work for any return type.
An example of such a type is (β : Type u) → (α → β) → (ε → β) → β
.
ExceptCpsT
is a transformer that can be applied to any monad, so ExceptCpsT ε m α
is actually defined as (β : Type u) → (α → m β) → (ε → m β) → m β
.
Exception monads in continuation passing style have different performance characteristics than Except
-based state monads; for some applications, it may be worth benchmarking them.
🔗defExceptCpsT.{u, v} (ε : Type u)
(m : Type u → Type v) (α : Type u)
: Type (max (u + 1) v)
🔗defExceptCpsT.runCatch.{u_1, u_2}
{m : Type u_1 → Type u_2} {α : Type u_1}
[Monad m] (x : ExceptCpsT α m α) : m α
🔗defExceptCpsT.runK.{u, u_1} {m : Type u → Type u_1}
{β ε α : Type u} (x : ExceptCpsT ε m α)
(s : ε) (ok : α → m β) (error : ε → m β)
: m β
🔗defExceptCpsT.run.{u, u_1} {m : Type u → Type u_1}
{ε α : Type u} [Monad m]
(x : ExceptCpsT ε m α) : m (Except ε α)
🔗defExceptCpsT.lift.{u_1, u_2}
{m : Type u_1 → Type u_2} {α ε : Type u_1}
[Monad m] (x : m α) : ExceptCpsT ε m α
5.5.8. Combined Error and State Monads
The EStateM
monad has both exceptions and mutable state.
EStateM ε σ α
is logically equivalent to ExceptT ε (StateM σ) α
.
While ExceptT ε (StateM σ)
evaluates to the type σ → Except ε α × σ
, the type EStateM ε σ α
evaluates to σ → EStateM.Result ε σ α
.
EStateM.Result
is an inductive type that's very similar to Except
, except both constructors have an additional field for the state.
In compiled code, this representation removes one level of indirection from each monadic bind.
🔗defEStateM.{u} (ε σ α : Type u) : Type u
EStateM ε σ
is a combined error and state monad, equivalent to
ExceptT ε (StateM σ)
but more efficient.
🔗inductive typeEStateM.Result.{u} (ε σ α : Type u) : Type u
Result ε σ α
is equivalent to Except ε α × σ
, but using a single
combined inductive yields a more efficient data representation.
Constructors
-
ok.{u} {ε σ α : Type u}
: α → σ → EStateM.Result ε σ α
A success value of type α
, and a new state σ
.
-
error.{u} {ε σ α : Type u}
: ε → σ → EStateM.Result ε σ α
A failure value of type ε
, and a new state σ
.
🔗defEStateM.run.{u} {ε σ α : Type u}
(x : EStateM ε σ α) (s : σ)
: EStateM.Result ε σ α
Execute an EStateM
on initial state s
to get a Result
.
🔗defEStateM.run'.{u} {ε σ α : Type u}
(x : EStateM ε σ α) (s : σ) : Option α
Execute an EStateM
on initial state s
for the returned value α
.
If the monadic action throws an exception, returns none
instead.
🔗defEStateM.adaptExcept.{u} {ε σ α ε' : Type u}
(f : ε → ε') (x : EStateM ε σ α)
: EStateM ε' σ α
Map the exception type of a EStateM ε σ α
by a function f : ε → ε'
.
🔗defEStateM.fromStateM {ε σ α : Type}
(x : StateM σ α) : EStateM ε σ α
5.5.8.1. State Rollback
Composing StateT
and ExceptT
in different orders causes exceptions to interact differently with state.
In one ordering, state changes are rolled back when exceptions are caught; in the other, they persist.
The latter option matches the semantics of most imperative programming languages, but the former is very useful for search-based problems.
Often, some but not all state should be rolled back; this can be achieved by “sandwiching” ExceptT
between two separate uses of StateT
.
To avoid yet another layer of indirection via the use of StateT σ (EStateM ε σ') α
, EStateM
offers the EStateM.Backtrackable
type class.
This class specifies some part of the state that can be saved and restored.
EStateM
then arranges for the saving and restoring to take place around error handling.
🔗type classEStateM.Backtrackable.{u}
(δ : outParam (Type u)) (σ : Type u)
: Type u
Auxiliary instance for saving/restoring the "backtrackable" part of the state.
Here σ
is the state, and δ
is some subpart of it, and we have a
getter and setter for it (a "lens" in the Haskell terminology).
Instance Constructor
Methods
save | : | σ → δ |
|
save s : δ retrieves a copy of the backtracking state out of the state.
|
restore | : | σ → δ → σ |
|
restore (s : σ) (x : δ) : σ applies the old backtracking state x to
the state s to get a backtracked state s' .
|
There is a universally-applicable instance of Backtrackable
that neither saves nor restores anything.
Because instance synthesis chooses the most recent instance first, the universal instance is used only if no other instance has been defined.
🔗defEStateM.nonBacktrackable.{u} {σ : Type u}
: EStateM.Backtrackable PUnit σ
Dummy default instance. This makes every σ
trivially "backtrackable"
by doing nothing on backtrack. Because this is the first declared instance
of Backtrackable _ σ
, it will be picked only if there are no other
Backtrackable _ σ
instances registered.
5.5.8.2. Implementations
These functions are typically not called directly, but rather are accessed through their corresponding type classes.
🔗defEStateM.map.{u} {ε σ α β : Type u} (f : α → β)
(x : EStateM ε σ α) : EStateM ε σ β
The map
operation of the EStateM
monad.
🔗defEStateM.pure.{u} {ε σ α : Type u} (a : α)
: EStateM ε σ α
The pure
operation of the EStateM
monad.
🔗defEStateM.bind.{u} {ε σ α β : Type u}
(x : EStateM ε σ α) (f : α → EStateM ε σ β)
: EStateM ε σ β
The bind
operation of the EStateM
monad.
🔗defEStateM.seqRight.{u} {ε σ α β : Type u}
(x : EStateM ε σ α)
(y : Unit → EStateM ε σ β) : EStateM ε σ β
The seqRight
operation of the EStateM
monad.
🔗defEStateM.tryCatch.{u} {ε σ δ : Type u}
[EStateM.Backtrackable δ σ] {α : Type u}
(x : EStateM ε σ α)
(handle : ε → EStateM ε σ α) : EStateM ε σ α
Implementation of tryCatch
for EStateM
where the state is Backtrackable
.
🔗defEStateM.throw.{u} {ε σ α : Type u} (e : ε)
: EStateM ε σ α
The throw
operation of the EStateM
monad.
🔗defEStateM.get.{u} {ε σ : Type u} : EStateM ε σ σ
The get
operation of the EStateM
monad.
🔗defEStateM.set.{u} {ε σ : Type u} (s : σ)
: EStateM ε σ PUnit
The set
operation of the EStateM
monad.
🔗defEStateM.modifyGet.{u} {ε σ α : Type u}
(f : σ → α × σ) : EStateM ε σ α
The modifyGet
operation of the EStateM
monad.