| Safe Haskell | Safe |
|---|---|
| Language | Haskell2010 |
Control.Monad.Operational.Higher
Description
Introduction
This module gives an alternative to the Operational package \[1\], in which
instructions can be higher-order functors, parameterized on the program monad
that they are part of. This makes it possible to define instruction sets
compositionally using :+:. In the normal Operational, this could be done
for simple instructions, but here it can be done even for "control
instructions" -- instructions that take programs as arguments.
For general information about the ideas behind this module, see the Operational package \[1\].
\[1\] http://hackage.haskell.org/package/operational
Example
(Full code found in https://github.com/emilaxelsson/operational-alacarte/blob/master/examples/Simple.hs.)
An "if" instruction can be defined as follows:
data If fs a where If :: ExpBool-> prog a -> prog a -> If (Param1prog) a
Note the use of the type parameter prog to refer to sub-programs. (Exp is
some type representing pure expressions.)
The type ( can be seen as a type-level list with one element
Param1 prog)prog. (See Data.ALaCarte for more details on parameter lists.)
We can now make program types that combine several instructions; e.g.:
type MyProgram =Program(If:+:Loop:+:...)Param0
Program is a recursive type that sets the type of the sub-programs of If
(and Loop, etc.) to MyProgram. With the original Operational package, we
would have to hard-code a specific type for the sub-programs of If (or make
MyProgram a recursive newtype, as noted by the author of Operational).
Interpretation of Program in a monad m can be done using
interpret:: (Interpi m fs,HFunctori,Monadm) =>Programi fs a -> m a
In order to use this function, If needs to be an instance of Interp and
HFunctor. The HFunctor instance is straightforward:
instanceHFunctorIf wherehfmapf (If c thn els) = If c (f thn) (f els)
The Interp type class is parameterized both on the instruction type and the
destination monad. For example, interpretation of If in the IO monad
might look as follows:
instanceInterpIfIOfs whereinterp(If c thn els) = if eval c then thn else els
(Here eval is the evaluator for the expression languauge Exp.)
The Interp class distributes over :+: which means that it is possible to
interpret any expression type (I1 to :+: I2 :+: I3 :+: ...)IO, as
long as the individual instructions (I1, I2, etc.) have Interp
instances for IO.
Bi-functorial instructions
The type ( in the result of Param1 prog)If above says that the
instruction has one sub-structure whose type is prog. The advanced example
https://github.com/emilaxelsson/operational-alacarte/blob/master/examples/Advanced.hs
shows a version of If that has two parameters:
If :: expBool-> prog a -> prog a -> If (Param2prog exp) a
prog represents sub-programs and exp represents expressions (Exp Bool
has been replaced with exp Bool).
This new If instruction is a higher-order bi-functor (see HBifunctor).
The advantage of parameterizing instructions on expressions is that it lets
us define generic functions, such as interpretBi, which decouple the
interpretation of instructions from the interpretation of expressions.
Generalized interface
We have seen two examples of If with a parameter list of one and two
arguments respectively (( and Param1 prog)(). There
is nothing stopping us from having instructions with more parameters. For
example, we could make a version of Param2 prog exp)If that takes an extra type class
constraint pred as parameter:
If :: pred a => expBool-> prog a -> prog a -> If (Param3prog exp pred) a
(See the documentation to Data.ALaCarte regarding why it is a good idea to
make pred part of the parameter list rather than just making it a separate
parameter.)
In fact, it is possible to have arbitrarily many parameters to instructions
(but the type synonyms for parameter lists stop at Param4).
The functions and types in this module (and Data.ALaCarte) are designed to
be generic in the sense that things that work for parameter lists of N
elements also work for parameter lists of more elements. For example, the
function interpret mentioned above requires the instruction i to be a
higher-order functor, but it also works for high-order bi-functors, and for
the last version of If that has an additional parameter pred.
Typical use
Here we give specialized type signatures for a selection of functions for different uses of the general interface.
.
Functorial instructions with no extra parameters
This scenario is used in https://github.com/emilaxelsson/operational-alacarte/blob/master/examples/Simple.hs.
singleton:: instr (Param1(ProgramTinstrParam0m)) a ->ProgramTinstrParam0m ainterpretWithMonad:: (HFunctorinstr,Monadm) => (forall b . instr (Param1m) b -> m b) ->PrograminstrParam0a -> m ainterpret:: (Interpi mParam0,HFunctori,Monadm) =>ProgramiParam0a -> m a
.
Functorial instructions with extra parameter
Like the previous scenario but with an extra parameter p (poly-kinded) that instructions can use for anything.
singleton:: instr (Param2(ProgramTinstr (Param1p) m) p) a ->ProgramTinstr (Param1p) m ainterpretWithMonad:: (HFunctorinstr,Monadm) => (forall b . instr (Param2m p) b -> m b) ->Programinstr (Param1p) a -> m ainterpret:: (Interpi m (Param1p),HFunctori,Monadm) =>Programi (Param1p) a -> m a
.
Bi-functorial instructions with no extra parameters
This scenario is used in https://github.com/emilaxelsson/operational-alacarte/blob/master/examples/Advanced.hs.
The parameter exp is an extra interpreted structure that e.g. can represent expressions.
singleton:: instr (Param2(ProgramTinstr (Param1exp) m) exp) a ->ProgramTinstr (Param1exp) m ainterpretWithMonadBi:: (HBifunctorinstr,Functorm,Monadm) => (forall b . exp b -> m b) -> (forall b . instr (Param2m m) b -> m b) ->Programinstr (Param1exp) a -> m ainterpret:: (InterpBii mParam0,HBifunctori,Functorm,Monadm) => (forall b . exp b -> m b) ->Programi (Param1exp) a -> m a
.
Bi-functorial instructions with extra parameter
Like the previous scenario but with an extra parameter p (poly-kinded) that instructions can use for anything.
singleton:: instr (Param3(ProgramTinstr (Param2exp p) m) exp p) a ->ProgramTinstr (Param2exp p) m ainterpretWithMonadBi:: (HBifunctorinstr,Functorm,Monadm) => (forall b . exp b -> m b) -> (forall b . instr (Param3m m p) b -> m b) ->Programinstr (Param2exp p) a -> m ainterpret:: (InterpBii m (Param1p),HBifunctori,Functorm,Monadm) => (forall b . exp b -> m b) ->Programi (Param2exp p) a -> m a
- module Control.Monad
- module Data.ALaCarte
- data ProgramT instr fs m a
- type Program instr fs = ProgramT instr fs Identity
- singleton :: instr '(ProgramT instr fs m, fs) a -> ProgramT instr fs m a
- singleInj :: i :<: instr => i '(ProgramT instr fs m, fs) a -> ProgramT instr fs m a
- liftProgram :: forall instr fs m a. (HFunctor instr, Monad m) => Program instr fs a -> ProgramT instr fs m a
- interpretWithMonadT :: forall instr fs m n a. (HFunctor instr, Monad m) => (forall b. instr '(m, fs) b -> m b) -> (forall b. n b -> m b) -> ProgramT instr fs n a -> m a
- interpretWithMonad :: (HFunctor instr, Monad m) => (forall b. instr '(m, fs) b -> m b) -> Program instr fs a -> m a
- class Interp instr m fs where
- interpretT :: (Interp i m fs, HFunctor i, Monad m) => (forall b. n b -> m b) -> ProgramT i fs n a -> m a
- interpret :: (Interp i m fs, HFunctor i, Monad m) => Program i fs a -> m a
- data ProgramViewT instr fs m a where
- Return :: a -> ProgramViewT instr fs m a
- (:>>=) :: instr '(ProgramT instr fs m, fs) b -> (b -> ProgramT instr fs m a) -> ProgramViewT instr fs m a
- type ProgramView instr fs = ProgramViewT instr fs Identity
- type ProgViewT instr m = ProgramViewT instr () m
- type ProgView instr = ProgramView instr ()
- viewT :: Monad m => ProgramT instr fs m a -> m (ProgramViewT instr fs m a)
- view :: HFunctor instr => Program instr fs a -> ProgramView instr fs a
- unview :: Monad m => ProgramViewT instr fs m a -> ProgramT instr fs m a
- interpretWithMonadBiT :: (HBifunctor instr, Functor m, Monad m, Monad n) => (forall b. exp b -> m b) -> (forall b. instr '(m, '(m, fs)) b -> m b) -> (forall b. n b -> m b) -> ProgramT instr '(exp, fs) n a -> m a
- interpretWithMonadBi :: (HBifunctor instr, Functor m, Monad m) => (forall b. exp b -> m b) -> (forall b. instr '(m, '(m, fs)) b -> m b) -> Program instr '(exp, fs) a -> m a
- class InterpBi instr m fs where
- interpretBiT :: (InterpBi i m fs, HBifunctor i, Functor m, Monad m, Monad n) => (forall b. exp b -> m b) -> (forall b. n b -> m b) -> ProgramT i '(exp, fs) n a -> m a
- interpretBi :: (InterpBi i m fs, HBifunctor i, Functor m, Monad m) => (forall b. exp b -> m b) -> Program i '(exp, fs) a -> m a
- class HBifunctor i => Reexpressible i instr env where
- reexpress :: (Reexpressible instr1 instr2 (), Monad m) => (forall b. exp1 b -> ProgramT instr2 '(exp2, fs) m (exp2 b)) -> ProgramT instr1 '(exp1, fs) m a -> ProgramT instr2 '(exp2, fs) m a
- reexpressEnv :: (Reexpressible instr1 instr2 env, Monad m) => (forall b. exp1 b -> ReaderT env (ProgramT instr2 '(exp2, fs) m) (exp2 b)) -> ProgramT instr1 '(exp1, fs) m a -> ReaderT env (ProgramT instr2 '(exp2, fs) m) a
Documentation
module Control.Monad
module Data.ALaCarte
Program monad
data ProgramT instr fs m a Source #
Representation of programs parameterized by the primitive instructions (transformer version)
type Program instr fs = ProgramT instr fs Identity Source #
Representation of programs parameterized by the primitive instructions
singleton :: instr '(ProgramT instr fs m, fs) a -> ProgramT instr fs m a Source #
Make a program from a single instruction
singleInj :: i :<: instr => i '(ProgramT instr fs m, fs) a -> ProgramT instr fs m a Source #
Make a program from a single instruction
Interpretation
Lift a program to a program transformer
Arguments
| :: (HFunctor instr, Monad m) | |
| => (forall b. instr '(m, fs) b -> m b) | Interpretation of instructions |
| -> (forall b. n b -> m b) | Interpretation of the underlying monad |
| -> ProgramT instr fs n a | |
| -> m a |
Interpret a program in a monad
Arguments
| :: (HFunctor instr, Monad m) | |
| => (forall b. instr '(m, fs) b -> m b) | Interpretation of instructions |
| -> Program instr fs a | |
| -> m a |
Interpret a program in a monad
class Interp instr m fs where Source #
represents the fact that Interp instr m fsinstr can be interpreted
in the monad m
Minimal complete definition
Arguments
| :: (Interp i m fs, HFunctor i, Monad m) | |
| => (forall b. n b -> m b) | Interpretation of the underlying monad |
| -> ProgramT i fs n a | |
| -> m a |
Interpret a program in a monad. The interpretation of instructions is
provided by the Interp class.
interpret :: (Interp i m fs, HFunctor i, Monad m) => Program i fs a -> m a Source #
Interpret a program in a monad. The interpretation of instructions is
provided by the Interp class.
data ProgramViewT instr fs m a where Source #
View type for inspecting the first instruction
Constructors
| Return :: a -> ProgramViewT instr fs m a | |
| (:>>=) :: instr '(ProgramT instr fs m, fs) b -> (b -> ProgramT instr fs m a) -> ProgramViewT instr fs m a |
type ProgramView instr fs = ProgramViewT instr fs Identity Source #
View type for inspecting the first instruction
type ProgViewT instr m = ProgramViewT instr () m Source #
type ProgView instr = ProgramView instr () Source #
viewT :: Monad m => ProgramT instr fs m a -> m (ProgramViewT instr fs m a) Source #
View function for inspecting the first instruction
view :: HFunctor instr => Program instr fs a -> ProgramView instr fs a Source #
View function for inspecting the first instruction
unview :: Monad m => ProgramViewT instr fs m a -> ProgramT instr fs m a Source #
Turn a ProgramViewT back to a Program
Bi-functorial instructions
interpretWithMonadBiT Source #
Arguments
| :: (HBifunctor instr, Functor m, Monad m, Monad n) | |
| => (forall b. exp b -> m b) | Interpretation of the |
| -> (forall b. instr '(m, '(m, fs)) b -> m b) | Interpretation of instructions |
| -> (forall b. n b -> m b) | Interpretation of the underlying monad |
| -> ProgramT instr '(exp, fs) n a | |
| -> m a |
Bi-functorial version of interpretWithMonadT
Bi-functorial instructions are of the form instr '(prog, '(exp, ...)) a,
where prog is a representation of sub-programs, and exp is a
representation of some other sub-structure, e.g. expressions.
interpretWithMonadBiT allows interpreting both these kinds of
sub-structures in a generic way.
Arguments
| :: (HBifunctor instr, Functor m, Monad m) | |
| => (forall b. exp b -> m b) | Interpretation of the |
| -> (forall b. instr '(m, '(m, fs)) b -> m b) | Interpretation of instructions |
| -> Program instr '(exp, fs) a | |
| -> m a |
Bi-functorial version of interpretWithMonad
See explanation of interpretWithMonadBiT.
class InterpBi instr m fs where Source #
represents the fact that the bi-functorial
instruction InterpBi instr m fsinstr can be interpreted in the monad m
Minimal complete definition
Arguments
| :: (InterpBi i m fs, HBifunctor i, Functor m, Monad m, Monad n) | |
| => (forall b. exp b -> m b) | Interpretation of the |
| -> (forall b. n b -> m b) | Interpretation of the underlying monad |
| -> ProgramT i '(exp, fs) n a | |
| -> m a |
Interpret a program in a monad. The interpretation of instructions is
provided by the InterpBi class.
Arguments
| :: (InterpBi i m fs, HBifunctor i, Functor m, Monad m) | |
| => (forall b. exp b -> m b) | Interpretation of the |
| -> Program i '(exp, fs) a | |
| -> m a |
Interpret a program in a monad. The interpretation of instructions is
provided by the InterpBi class.
class HBifunctor i => Reexpressible i instr env where Source #
Reexpressible types
i is a bi-functorial instruction where, in the type i '(p,'(e1,...)) a,
sub-structure e1 can be converted to a different sub-structure e2.
e1 and e2 typically represent expressions; hence the name
"reexpressible".
Minimal complete definition
Methods
reexpressInstrEnv :: Monad m => (forall b. exp1 b -> ReaderT env (ProgramT instr '(exp2, fs) m) (exp2 b)) -> i '(ReaderT env (ProgramT instr '(exp2, fs) m), '(exp1, fs)) a -> ReaderT env (ProgramT instr '(exp2, fs) m) a Source #
Rewrite an instruction changing its "expression" sub-structure
As an example of how to define this function, take the following instruction that just puts a tag on a sub-program:
data Tag fs a
where
Tag :: String -> prog () -> Tag (Param2 prog exp) ()
To define reexpressInstrEnv we have to use a combination of ReaderT
and runReaderT:
instance (Tag:<:instr) =>ReexpressibleTag instr wherereexpressInstrEnvreexp (Tag tag prog) =ReaderT$env ->singleInj$Tag tag (fliprunReaderTenv prog)
Instances
| (Reexpressible k k1 i1 instr env, Reexpressible k k1 i2 instr env) => Reexpressible k k1 ((:+:) * (* -> *, (k -> *, k1)) i1 i2) instr env Source # | |
Arguments
| :: (Reexpressible instr1 instr2 (), Monad m) | |
| => (forall b. exp1 b -> ProgramT instr2 '(exp2, fs) m (exp2 b)) | Conversion of expressions |
| -> ProgramT instr1 '(exp1, fs) m a | |
| -> ProgramT instr2 '(exp2, fs) m a |
Rewrite a program changing its expression type (assuming that the second sub-structure of the instruction type represents expressions)
Conversion of expressions is done in the target monad, so pure expressions are allowed to expand to monadic code. This can be used e.g. to "compile" complex expressions into simple expressions with supporting monadic code.
Arguments
| :: (Reexpressible instr1 instr2 env, Monad m) | |
| => (forall b. exp1 b -> ReaderT env (ProgramT instr2 '(exp2, fs) m) (exp2 b)) | Conversion of expressions |
| -> ProgramT instr1 '(exp1, fs) m a | |
| -> ReaderT env (ProgramT instr2 '(exp2, fs) m) a |
Rewrite a program changing its expression type (assuming that the second sub-structure of the instruction type represents expressions)
Conversion of expressions is done in the target monad, so pure expressions are allowed to expand to monadic code. This can be used e.g. to "compile" complex expressions into simple expressions with supporting monadic code.