Safe Haskell | Trustworthy |
---|---|

Language | Haskell2010 |

Original work available at http://okmij.org/ftp/Haskell/extensible/Eff.hs. This module implements extensible effects as an alternative to monad transformers, as described in http://okmij.org/ftp/Haskell/extensible/exteff.pdf.

Extensible Effects are implemented as typeclass constraints on an Eff[ect] datatype. A contrived example is:

{-# LANGUAGE FlexibleContexts #-} import Control.Eff import Control.Eff.Lift import Control.Eff.State import Control.Monad (void) import Data.Typeable -- Write the elements of a list of numbers, in order. writeAll :: (Typeable a, Member (Writer a) e) => [a] -> Eff e () writeAll = mapM_ putWriter -- Add a list of numbers to the current state. sumAll :: (Typeable a, Num a, Member (State a) e) => [a] -> Eff e () sumAll = mapM_ (onState . (+)) -- Write a list of numbers and add them to the current state. writeAndAdd :: (Member (Writer Integer) e, Member (State Integer) e) => [Integer] -> Eff e () writeAndAdd l = do writeAll l sumAll l -- Sum a list of numbers. sumEff :: (Num a, Typeable a) => [a] -> a sumEff l = let (s, ()) = run $ runState 0 $ sumAll l in s -- Safely get the last element of a list. -- Nothing for empty lists; Just the last element otherwise. lastEff :: Typeable a => [a] -> Maybe a lastEff l = let (a, ()) = run $ runWriter $ writeAll l in a -- Get the last element and sum of a list lastAndSum :: (Typeable a, Num a) => [a] -> (Maybe a, a) lastAndSum l = let (lst, (total, ())) = run $ runWriter $ runState 0 $ writeAndAdd l in (lst, total)

- type Eff r = Codensity (VE r)
- type VE r = Free (Union r)
- data Free f a :: (* -> *) -> * -> *
- class (Member' t r ~ True) => Member t r
- class Member t r => SetMember set t r | r set -> t
- data Union r v
- data a :> b
- inj :: (Functor t, Typeable t, Member t r) => t v -> Union r v
- prj :: (Typeable t, Member t r) => Union r v -> Maybe (t v)
- prjForce :: (Typeable t, Member t r) => Union r v -> (t v -> a) -> a
- decomp :: Typeable t => Union (t :> r) v -> Either (Union r v) (t v)
- send :: (forall w. (a -> VE r w) -> Union r (VE r w)) -> Eff r a
- admin :: Eff r w -> VE r w
- run :: Eff () w -> w
- interpose :: (Typeable t, Functor t, Member t r) => Union r v -> (v -> Eff r a) -> (t v -> Eff r a) -> Eff r a
- handleRelay :: Typeable t => Union (t :> r) v -> (v -> Eff r a) -> (t v -> Eff r a) -> Eff r a
- unsafeReUnion :: Union r w -> Union t w

# Documentation

type Eff r = Codensity (VE r) Source

Basic datatype returned by all computations with extensible effects. The

type is a type synonym where the type `Eff`

r`r`

is the type of effects
that can be handled, and the missing type `a`

(from the type application) is
the type of value that is returned.

As is made explicit below, the `Eff`

type is simply the application of the
Codensity transformer to `VE`

:

type`Eff`

r a =`Codensity`

(`VE`

r) a

This is done to gain the asymptotic speedups for scenarios where there is a
single `execution`

stage where the built up monadic computation gets
executed. For scenarios where the computation execution and building stages
are interspersed, the reflection without remorse techniques would be a
better fit. See https://github.com/atzeus/reflectionwithoutremorse.

data Free f a :: (* -> *) -> * -> *

The `Free`

`Monad`

for a `Functor`

`f`

.

*Formally*

A `Monad`

`n`

is a free `Monad`

for `f`

if every monad homomorphism
from `n`

to another monad `m`

is equivalent to a natural transformation
from `f`

to `m`

.

*Why Free?*

Every "free" functor is left adjoint to some "forgetful" functor.

If we define a forgetful functor `U`

from the category of monads to the category of functors
that just forgets the `Monad`

, leaving only the `Functor`

. i.e.

U (M,`return`

,`join`

) = M

then `Free`

is the left adjoint to `U`

.

Being `Free`

being left adjoint to `U`

means that there is an isomorphism between

in the category of monads and `Free`

f -> m`f -> U m`

in the category of functors.

Morphisms in the category of monads are `Monad`

homomorphisms (natural transformations that respect `return`

and `join`

).

Morphisms in the category of functors are `Functor`

homomorphisms (natural transformations).

Given this isomorphism, every monad homomorphism from

to `Free`

f`m`

is equivalent to a natural transformation from `f`

to `m`

Showing that this isomorphism holds is left as an exercise.

In practice, you can just view a

as many layers of `Free`

f a`f`

wrapped around values of type `a`

, where
`(`

performs substitution and grafts new layers of `>>=`

)`f`

in for each of the free variables.

This can be very useful for modeling domain specific languages, trees, or other constructs.

This instance of `MonadFree`

is fairly naive about the encoding. For more efficient free monad implementation see Control.Monad.Free.Church, in particular note the `improve`

combinator.
You may also want to take a look at the `kan-extensions`

package (http://hackage.haskell.org/package/kan-extensions).

A number of common monads arise as free monads,

MonadTrans Free | This is not a true monad transformer. It is only a monad transformer "up to |

(Functor m, MonadState s m) => MonadState s (Free m) | |

(Functor m, MonadReader e m) => MonadReader e (Free m) | |

Functor f => MonadFree f (Free f) | |

(Functor m, MonadError e m) => MonadError e (Free m) | |

(Functor m, MonadWriter e m) => MonadWriter e (Free m) | |

(MonadBase b m, Typeable (* -> *) m, SetMember ((* -> *) -> * -> *) Lift (Lift m) r) => MonadBase b (Eff r) | |

Alternative v => Alternative (Free v) | This violates the Alternative laws, handle with care. |

Functor f => Monad (Free f) | |

Functor f => Functor (Free f) | |

Functor f => MonadFix (Free f) | |

(Functor v, MonadPlus v) => MonadPlus (Free v) | This violates the MonadPlus laws, handle with care. |

Functor f => Applicative (Free f) | |

Foldable f => Foldable (Free f) | |

Traversable f => Traversable (Free f) | |

(Typeable (* -> *) m, MonadIO m, SetMember ((* -> *) -> * -> *) Lift (Lift m) r) => MonadIO (Eff r) | |

(Functor m, MonadCont m) => MonadCont (Free m) | |

(Functor f, Eq1 f) => Eq1 (Free f) | |

(Functor f, Ord1 f) => Ord1 (Free f) | |

(Functor f, Show1 f) => Show1 (Free f) | |

(Functor f, Read1 f) => Read1 (Free f) | |

Traversable1 f => Traversable1 (Free f) | |

Foldable1 f => Foldable1 (Free f) | |

Functor f => Apply (Free f) | |

Functor f => Bind (Free f) | |

(Eq (f (Free f a)), Eq a) => Eq (Free f a) | |

(Ord (f (Free f a)), Ord a) => Ord (Free f a) | |

(Read (f (Free f a)), Read a) => Read (Free f a) | |

(Show (f (Free f a)), Show a) => Show (Free f a) | |

Typeable ((* -> *) -> * -> *) Free |

class Member t r => SetMember set t r | r set -> t Source

`SetMember`

is similar to `Member`

, but it allows types to belong to a
"set". For every set, only one member can be in `r`

at any given time.
This allows us to specify exclusivity and uniqueness among arbitrary effects:

-- Terminal effects (effects which must be run last) data Terminal -- Make Lifts part of the Terminal effects set. -- The fundep assures that there can only be one Terminal effect for any r. instance Member (Lift m) r => SetMember Terminal (Lift m) r -- Only allow a single unique Lift effect, by making a "Lift" set. instance Member (Lift m) r => SetMember Lift (Lift m) r

MemberU k set t r => SetMember (k -> * -> *) set t r |

Parameter `r`

is phantom: it just tells what could be in the union.
Where `r`

is `t1 :> t2 ... :> tn`

,

can be constructed with a
value of type `Union`

r v`ti v`

.
Ideally, we should be able to add the constraint

.`Member`

t r

NOTE: exposing the constructor below allows users to bypass the type
system. See `unsafeReUnion`

for example.

prj :: (Typeable t, Member t r) => Union r v -> Maybe (t v) Source

Try extracting the contents of a Union as a given type.

prjForce :: (Typeable t, Member t r) => Union r v -> (t v -> a) -> a Source

Extract the contents of a Union as a given type. If the Union isn't of that type, a runtime error occurs.

decomp :: Typeable t => Union (t :> r) v -> Either (Union r v) (t v) Source

Try extracting the contents of a Union as a given type. If we can't, return a reduced Union that excludes the type we just checked.

send :: (forall w. (a -> VE r w) -> Union r (VE r w)) -> Eff r a Source

Given a method of turning requests into results, we produce an effectful computation.

admin :: Eff r w -> VE r w Source

Tell an effectful computation that you're ready to start running effects and return a value.

interpose :: (Typeable t, Functor t, Member t r) => Union r v -> (v -> Eff r a) -> (t v -> Eff r a) -> Eff r a Source

Given a request, either handle it or relay it. Both the handler and the relay can produce the same type of request that was handled.

:: Typeable t | |

=> Union (t :> r) v | Request |

-> (v -> Eff r a) | Relay the request |

-> (t v -> Eff r a) | Handle the request of type t |

-> Eff r a |

Given a request, either handle it or relay it.

unsafeReUnion :: Union r w -> Union t w Source

Juggle types for a Union. Use cautiously.