graft-0.0.0
Safe HaskellSafe-Inferred
LanguageHaskell2010

Effect.TH

Synopsis

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:

  1. 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 constraint EffectInject (MonadBarEffect b) ops. This macro assumes that the only way for an AST to become a MonadX for some X is to contain the correct effect type MonadXEffect. That is, we employ the same naming conventions as explained at defineEffectType.
  1. 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.

makeReification Source #

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 ops

-> 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.

makeInterpretation Source #

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 m

-> 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 m in 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. ->, or ArrowT 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 *