Safe Haskell | Safe-Inferred |
---|---|
Language | Haskell2010 |
Effect.TH
Documentation
defineEffectType :: Name -> Q [Dec] Source #
Generate the effect type corresponding to a class.
By, example, given
class (...) => Foo a b m where foo :: a -> b -> m () bar :: forall c. Ord c => (c -> m a) -> b -> m (c, Bool)
the macro
defineEffectType ''Foo
writes an effect type like
data FooEffect a b :: Effect where Foo :: a -> b -> FooEffect a b m () Bar :: forall c. Ord c => (c -> m a) -> b -> FooEffect a b m (c, Bool)
There are two naming conventions here:
naming convention 1: The effect type corresponding to the class X
is
called XEffect
. For example, MonadError
corresponds to
MonadErrorEffect
.
naming convention 2: The names of the constructors of XEffect
must be
exactly the names of the methods of the class X
, just starting with an
upper-case letter.
This macro furhtermore assumes that the last type variable in the the class
definition (here, that's m
) is of kind Type -> Type
. This makes sense
for our application, because our classes will normally be classes of monads.
makeEffect :: Name -> Name -> Q [Dec] Source #
Automatically write "reification" and "interpretation" instances for an effect type and its associated class of monads.
Assume a class definition like
class (SomeConstraint a, MonadBar b m) => MonadFoo a b m
and an effect type defined like
data MonadFooEffect a b :: Effect
Then the macro
makeEffect ''MonadFoo ''MonadFooEffect
will expand into two instance definitions:
- The "reification" instance
instance (SomeConstraint a, EffectInject (MonadBarEffect b) ops, EffectInject (MonadFooEffect a b) ops) => MonadFoo a b (AST ops)
says that an AST
whose list ops
of effect types contains
MonadFooEffect
is a MonadFoo
. In order for this instance to make sense,
though, we'll have to add at least satisfy the constraints that were already
there on the class definition of MonadFoo
. Therefore, we have to add
SomeConstraint a
,- a constraint that implies
MonadBar b (AST ops)
: That is the reason for the constraintEffectInject (MonadBarEffect b) ops
. This macro assumes that the only way for anAST
to become aMonadX
for someX
is to contain the correct effect typeMonadXEffect
. That is, we employ the same naming conventions as explained atdefineEffectType
.
- the "interpretation" instance
instance (MonadFoo a b m) => InterpretEffect m (MonadFooEffect a b)
says that for any MonadFoo a b m
, we can interpret the effects described
by MonadFooEffect a b
into m
.
remark for the general case: This macro works by using the "additional
constraints" arguments to makeReification
and makeInterpretation
. If you
want to generate the instances with other constraints, you'll have to use
these two macros directly.
Arguments
:: ([Name] -> Name -> Q Type) | additional constraints for the instance head, depending on the names of
extra type variables belonging to the effect type, and of |
-> Name | class name |
-> Name | the effect type |
-> Q [Dec] |
Write a "reification" instance for an effect type. Such an instance
allows writing AST
s containing effects of that type using the syntax of
a class like MonadError
, MonadState
...
For example, given the effect type
data ErrorEffect e m a where ThrowError :: e -> ErrorEffect e m a CatchError :: m a -> (e -> m a) -> ErrorEffect e m a
the TH splice
makeReification (\[e] ops -> [t|SomeConstraint $(varT e) $(varT ops)|]) ''MonadError ''ErrorEffect
will expand into an instance like
instance (SomeConstraint e ops, EffectInject (ErrorEffect e) ops) => MonadError e (AST ops) where throwError err = astInject (ThrowError err) catchError acts handler = astInject (CatchError acts handler)
For this to work, it is expected that:
- The first quoted type passed to the splice is the class that you want to
use for your syntax. Its kind should be
(* -> *) -> Constraint
- The second quoted type is the effect type.
Its kind should be
(* -> *) -> * -> *
. - The constructor names of the effect type are exactly the method names of the class, just beginning with an upper case letter.
Arguments
:: ([Name] -> Name -> Q Type) | additional constraints for the instance head, depending on the names of
extra type variables belonging to the effect type, and of |
-> Name | class name |
-> Name | effect type name |
-> Q [Dec] |
Write an "interpretation" instance for an effect type. Such an instance
allows one to evaluate AST
s using the effect type. (For example, using interpretAST
)
For example, given the effect type
data ErrorEffect e m a where ThrowError :: e -> ErrorEffect e m a CatchError :: m a -> (e -> m a) -> ErrorEffect e m a
the TH splice
makeInterpretation (\[e] m -> [t|SomeConstraint $(varT e) $(varT m)|]) [t|ErrorEffect $(varT (mkName "e"))|]
will expand into an instance like
instance (SomeConstraint e m, MonadError e m) => InterpretEffect m (ErrorEffect e) where interpretEffect _ (ThrowError err) = throwError @e err interpretEffect evalAST (CatchError acts handler) = catchError @e (evalAST acts) (evalAST . handler)
For this to work, it is expected that:
- The first quoted type passed to the splice is the class of monads that yow
want to interpret the effect into. Its kind should be
(* -> *) -> Constraint
- The second quoted type is the effect type.
Its kind should be
(* -> *) -> * -> *
. - The constructor names of the effect type are exactly the method names of the class, just beginning with an upper case letter.
- The arguments of constructors of the effect type only use
m
in positive positions. This is not a restriction of the TemplateHaskell, but a restriction of the library. You can only "nest"AST
s in positive position. - For now, the TemplateHaskell works only if the arguments of constructors of the effect type only use the following type constructors:
- The name of the "nesting" monad (here, that's
m
) applied to some type - Function Types (i.e.
->
, orArrowT
in TH) - List Types (i.e.
ListT
in TH) Maybe
,Either
, or(,)
applied to some type(s)IO
applied to some type- Parenheses (i.e.
ParensT
in TH) - Type Variables (i.e.
VarT
in TH) - Type Constructors of types of kind
*