In Haskell, monadic types - types having an instance for the
Monad class - can be thought of as abstract descriptors of computations which are inherently composable - smaller monadic expressions (actions) can be used to build larger ones.
This monadic interface (as specified by
Monad) provides actions additional flexibility in separating:
- the time of composition: when it is defined;
- the time of execution: when it is used;
- the mode of computation: what else it can do, in addition to emitting its single (hence the name) output value.
Ideally, each monadic type in Haskell should satisfy the monad laws by providing two basic operations:
return :: Monad m => a -> m a:
another Haskell value result
:: m a
an action, merely returning the argument's value.
(>>=) :: Monad m => m a -> (a -> m b) -> m b:
:: m a
an action argument #2
:: (a -> m b)
a suitable Haskell function (a reaction) result
:: m b
another action, the result of binding the output value of argument #1
(the action) to the input of argument #2 (the reaction).
Together, these two operations allow many of the specific (and often-repetitive) details of the computational processes used by actions to be abstracted away. This often makes the monadic interface the preferred choice for abstract data types, where the inner workings of monadic actions must be kept private e.g. to ensure they work as intended.
As a reaction uses its argument to build a new monadic result, it can be thought of as an abstract constructor for new actions. In this way
return can then be seen as the trivial-action constructor for new actions: the monadic counterpart to the identity function
id :: a -> a id x = x
IO, in particular
IO type is special because a Haskell program is one large
IO action (usually) called
main - for any other
IO action to work, it should be part of the chain of I/O actions composed together to form
main. Execution of a Haskell program then starts by the Haskell implementation calling
main, according to the implementation's private definition of the abstract
Using monadic actions
Mis a monadic type;
x :: ais some other Haskell value;
m :: M ais an action;
k :: (a -> M b)is a reaction;
||(a new action)|
|| (an action whose output value is |
||(another new action)|
|| (the same as |
||(types don't match)|
|| (an action whose output value is also the same as |
|| (the same as |
infixr 0 $ ($) :: (a -> b) -> a -> b f $ x = f x infixr 1 =<< (=<<) :: (a -> M b) -> M a -> M b k =<< m = m >>= k infixl 4 <$> (<$>) :: (a -> b) -> M a -> M b f <$> m = m >>= \x -> return (f x) join :: M (M a) -> M a join m = m >>= \x -> x
Even though it is a purely-functional language, Haskell can facilitate the use of effect-based computations by presenting them as monadic actions (using a suitable type), with the monadic interface helping to maintain purity. This is the result of the aformentioned separation of concerns, most notably the time of composition. Monadic actions thus resemble programs in a particular EDSL (embedded domain-specific language), "embedded" because they are legitimate Haskell values which describe impure computations - no extraneous annotations are needed. This is how monadic types in Haskell help keep the pure and the impure apart. But not all monadic actions are impure - for them, the benefits of separating concerns are often combined with the automatic formation of a computational "pipeline".
Because they are very useful in practice but rather confronting for beginners, numerous monad tutorials have been written to help with the learning process.
Supplemental operations for monadic types (non-standard morphisms)
In addition to the two basic ones required by the monadic interface, each monadic type can have its own specific operations e.g. to provide access to and/or enable manipulation of data it implicitly carries which is specific to its nature; cause some specific side-effects; etc.
Running monadic actions
Some monadic types provide an extra operation for running actions, usually if they are purely defined by the type. This allows an action's output value to be obtained directly in Haskell, in contrast to
(>>=). While some of the predefined (e.g. from the
Prelude) monadic types in Haskell have such an operation (e.g.
Prelude.either), most do not.
Why this is expressly not a part of the monadic interface by now should be obvious - impure monadic actions could then be "hidden" inside seemingly-pure definitions, which would end any notion of purity in Haskell. This is why the only general-purpose way to use a monadic action's output value is to direct it to subsequent computations via the reaction which constructs them, through the use of