Safe Haskell | Safe |
---|---|
Language | Haskell2010 |
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 (Param1
prog) 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
:: (Interp
i m fs,HFunctor
i,Monad
m) =>Program
i 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:
instanceHFunctor
If wherehfmap
f (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:
instanceInterp
IfIO
fs 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 (Param2
prog 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 (Param3
prog 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
(ProgramT
instrParam0
m)) a ->ProgramT
instrParam0
m ainterpretWithMonad
:: (HFunctor
instr,Monad
m) => (forall b . instr (Param1
m) b -> m b) ->Program
instrParam0
a -> m ainterpret
:: (Interp
i mParam0
,HFunctor
i,Monad
m) =>Program
iParam0
a -> 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
(ProgramT
instr (Param1
p) m) p) a ->ProgramT
instr (Param1
p) m ainterpretWithMonad
:: (HFunctor
instr,Monad
m) => (forall b . instr (Param2
m p) b -> m b) ->Program
instr (Param1
p) a -> m ainterpret
:: (Interp
i m (Param1
p),HFunctor
i,Monad
m) =>Program
i (Param1
p) 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
(ProgramT
instr (Param1
exp) m) exp) a ->ProgramT
instr (Param1
exp) m ainterpretWithMonadBi
:: (HBifunctor
instr,Functor
m,Monad
m) => (forall b . exp b -> m b) -> (forall b . instr (Param2
m m) b -> m b) ->Program
instr (Param1
exp) a -> m ainterpret
:: (InterpBi
i mParam0
,HBifunctor
i,Functor
m,Monad
m) => (forall b . exp b -> m b) ->Program
i (Param1
exp) 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
(ProgramT
instr (Param2
exp p) m) exp p) a ->ProgramT
instr (Param2
exp p) m ainterpretWithMonadBi
:: (HBifunctor
instr,Functor
m,Monad
m) => (forall b . exp b -> m b) -> (forall b . instr (Param3
m m p) b -> m b) ->Program
instr (Param2
exp p) a -> m ainterpret
:: (InterpBi
i m (Param1
p),HBifunctor
i,Functor
m,Monad
m) => (forall b . exp b -> m b) ->Program
i (Param2
exp 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
:: (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
:: (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
:: (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
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 #
:: (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.
:: (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
:: (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.
:: (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".
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) =>Reexpressible
Tag instr wherereexpressInstrEnv
reexp (Tag tag prog) =ReaderT
$
env ->singleInj
$
Tag tag (flip
runReaderT
env prog)
(Reexpressible k k1 i1 instr env, Reexpressible k k1 i2 instr env) => Reexpressible k k1 ((:+:) * (* -> *, (k -> *, k1)) i1 i2) instr env Source # | |
:: (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.
:: (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.