Generalised_signatures.MonadsWe begin with the classic monad signature, with a single type parameter:
module type Monad = sig ... endThis signature is enough to include many sensible monad instances. Unfortunately, it can't be used to describe types that are monadic only when partially applied, like result.
We could define a name for the partially-applied operator that specialises result to a particular error type:
(* Variant type holding the set of possible program errors *)
type error = ..
type 'a or_error = ('a, error) resultbut life is too short to have each program define its own monad instance for its specific error cases. Instead, we can extend our Monad signature to carry an extra type parameter, held constant over each occurrence of t, that result can use to hold its error type. Since this extra type parameter isn't meaningful to the monad interface, we can consider it a phantom type parameter 'p.
This gives the following signature:
module type Monad_phantom = sig ... endNow we can provide a result instance for our monad signature:
module Result_phantom : Monad_phantom with type ('o, 'e) t := ('o, 'e) Stdlib.resultWe can go even further in adding type-level features to our monad signature. Some standard monad instances are like Result in being parameterised over some type p, but take this a step further by allowing p to change throughout the computation.
Consider the state monad, which threads a value of some type throughout a computation:
If we allow computations to change this type, then we'll need both an input and an output state type for each computation:
Now when we sequence computations with ( >>= ) we just need to connect the inputs up with the outputs:
val bind :
('a, 'input, 'middle) t ->
('a -> ('b, 'middle, 'output) t) ->
('b, 'input, 'output) tWhat we've described is sometimes called Atkey's indexed monad, and its interface captures even more sensible monad instances:
module type Monad_indexed = sig ... endNow that we've pulled together some different type-level implementations of monads, we can write a generalisation of them:
module type Monad_generalised = sig ... endFrom this, we can easily recover each of our monad variants:
module type Monad_simple' = sig ... endmodule type Monad_indexed' = sig ... endmodule type Monad_phantom' = sig ... endMonad_generalised admits the common monad instances:
module Identity : Monad_generalised with type ('a, _, _, _) t := 'amodule List : Monad_generalised with type ('a, _, _, _) t := 'a listmodule Result : Monad_generalised with type ('a, _, _, 'e) t := ('a, 'e) Stdlib.resultmodule Reader : Monad_generalised with type ('a, _, _, 's) t := 's -> 'aBoth forms of the state monad (indexed and non-indexed) add extra get and put operations to the monad interface:
module type State_generalised = sig ... endOnce again, we can recover the less general signatures with destructive substitution:
module type State_fixed' = sig ... endmodule type State_indexed' = sig ... end