| 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 anASTto become aMonadXfor someXis 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 ASTs 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 ASTs 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
min positive positions. This is not a restriction of the TemplateHaskell, but a restriction of the library. You can only "nest"ASTs 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.
->, orArrowTin TH) - List Types (i.e.
ListTin TH) Maybe,Either, or(,)applied to some type(s)IOapplied to some type- Parenheses (i.e.
ParensTin TH) - Type Variables (i.e.
VarTin TH) - Type Constructors of types of kind
*