| Safe Haskell | None |
|---|---|
| Language | Haskell2010 |
Language.Expression.Example
Description
Language.Expression provides a way of forming strongly types expression
languages from operators, via HFree. This module is an explanation of how
that works, via a simple example.
This module is intended to be read via Haddock.
Synopsis
- data SimpleOp (t :: Type -> Type) a where
- Add :: forall (t :: Type -> Type). t Integer -> t Integer -> SimpleOp t Integer
- Equal :: forall (t :: Type -> Type). t Integer -> t Integer -> SimpleOp t Bool
- IfThenElse :: forall (t :: Type -> Type). t Bool -> t Integer -> t Integer -> SimpleOp t Integer
- Literal :: forall (t :: Type -> Type). Integer -> SimpleOp t Integer
- data SimpleVar a where
- type SimpleExpr = HFree SimpleOp SimpleVar
- var :: String -> SimpleExpr Integer
- (^+) :: SimpleExpr Integer -> SimpleExpr Integer -> SimpleExpr Integer
- (^==) :: SimpleExpr Integer -> SimpleExpr Integer -> SimpleExpr Bool
- ifThenElse :: SimpleExpr Bool -> SimpleExpr Integer -> SimpleExpr Integer -> SimpleExpr Integer
- lit :: Integer -> SimpleExpr Integer
- exampleExpr :: SimpleExpr Integer
- exampleExpr2 :: SimpleExpr Integer
- evalXOrY :: SimpleVar a -> Maybe (Identity a)
- evalVarSymbolic :: SimpleVar a -> StateT (Map String (SBV Integer)) Symbolic (SBV a)
- evalSimpleExprSymbolic :: SimpleExpr a -> StateT (Map String (SBV Integer)) Symbolic (SBV a)
- examplePredicate :: Predicate
Operators
An operator is a type constructor op of kind (* -> *) -> * -> *. op t
a should be seen as a way of combining t-objects which has a result type
a.
SimpleOp is an example of an operator type.
data SimpleOp (t :: Type -> Type) a where Source #
An operator type has two arguments. t is a type constructor used to refer to
expressions. a is the semantic type of the expression formed by the operator.
Constructors
| Add :: forall (t :: Type -> Type). t Integer -> t Integer -> SimpleOp t Integer | Given two int expressions, we may add them. Notice how we refer to expressions
recursively with the type constructor parameter |
| Equal :: forall (t :: Type -> Type). t Integer -> t Integer -> SimpleOp t Bool | Given two int expressions, we may compare them for equality. Notice the types of the arguments to the operator do not appear in the result type at all. |
| IfThenElse :: forall (t :: Type -> Type). t Bool -> t Integer -> t Integer -> SimpleOp t Integer | Operator constructors can have any number of arguments, and you can mix argument types. |
| Literal :: forall (t :: Type -> Type). Integer -> SimpleOp t Integer | An operator does not have to actually combine expressions. It may produce an
expression from a basic value, i.e. a literal int. |
Type Classes
Most useful operators are instances of HFunctor (which provides hmap) and
HTraversable (which provides htraverse). These are higher-ranked analogues
of Functor and Traversable from base. Compare the type signatures of
fmap and hmap:
fmap:: (Functorf) => (a -> b) -> f a -> f bhmap:: (HFunctorh) => (forall b. s b -> t b) -> h s a -> h t a
fmap transforms the *-kinded type argument to f, while hmap transforms
the (* -> *)-kinded type constructor argument to h.
So in order for an operator to have an instance of HFunctor, it must be
possible to swap out sub-expressions inside it. This is the case for our
SimpleOp.
HTraversable has a similar relationship with Traversable. htraverse
adds an applicative context to hmap's transformations:
traverse:: (Traversablet,Applicativef) => (a -> f b) -> t a -> f (t b)htraverse:: (HTraversableh,Applicativef) => (forall b. s b -> f (t b)) -> h s a -> f (h t a)
There is a default implementation of hmap in terms of htraverse.
Variables
As well as operators, expressions contain variables. Variables are also strongly
typed. A variable type v has kind * -> *. The type parameter tells us the
type of the value that the variable is meant to represent.
data SimpleVar a where Source #
Notice that a is a phantom type here (and it will be for most variable type
constructors). It is only used to restrict the type of values that are
representable by these variables. In this case, we only want to be able to store
integers so the only constructor has return type Integer. It also contains a
String to be used as the variable name.
Expressions
A DSL
We can write simple wrapper functions to form a DSL for our expression language.
(^+) :: SimpleExpr Integer -> SimpleExpr Integer -> SimpleExpr Integer Source #
(^==) :: SimpleExpr Integer -> SimpleExpr Integer -> SimpleExpr Bool Source #
ifThenElse :: SimpleExpr Bool -> SimpleExpr Integer -> SimpleExpr Integer -> SimpleExpr Integer Source #
ifThenElse x y z =HWrap(IfThenElsex y z)
Example Expression
Expression Manipulation
If op is an HFunctor, then is an HFree opHMonad. HMonad defines
hpure (c.f. pure or return):
hpure:: (HMonadh) => t a -> h t areturn:: (Monadm) => a -> m a
(>>=) :: (Monadm) => m a -> (a -> m b) -> m b (^>>=) :: (HMonadh) => h s a -> (forall b. s b -> h t b) -> h t a
In the case of , HFree ophpure produces an expression containing a single
variable:
hpure:: v a ->HFreeop v a
Substitution
^>>= substitutes variables inside expressions.
Traversal
When op is an HTraversable, HFree is also an HTraversable. This can be
used, for example, to evaluate variables in an expression.
evalXOrY :: SimpleVar a -> Maybe (Identity a) Source #
This is a function that knows the value of variables with certain names. It will
return Nothing if it doesn't know how to evaluate a particular variable.
evalXOrY (SimpleVar"x") =Just1 evalXOrY (SimpleVar"y") =Just2 evalXOrY _ =Nothing
It might seem strange that we can return when the
function says it returns a Just (Identity 1) for polymorphic Maybe (Identity a)a. This
works because the constructor SimpleVar must be Integer-valued, so when we match
on it, GHC generates the constraint a ~ and it will happily accept an
IntegerInteger.
Notice that, for s ~ , SimpleVart ~ and Identityf ~ ,Maybe
evalXOrY :: forall a. s a -> f (t a)
This means that we can traverse using this function:
>>>htraverse evalXOrY exampleExprhtraverse evalXOrY exampleExpr :: Maybe (HFree SimpleOp Identity Integer) htraverse evalXOrY exampleExpr ~ Just (if (1 = 10) then 2 else 2 + 5)
>>>htraverse evalXOrY exampleExpr2htraverse evalXOrY exampleExpr2 :: Maybe (HFree SimpleOp Identity Integer) htraverse evalXOrY exampleExpr2 ~ Nothing
There was a variable (z) that evalXOrY didn't know how to evaluate, so the
traversal resulted in Nothing!
Evaluating Expressions
The HFoldableAt type class provides the mechanism for evaluating operators,
and hence expressions.
hfoldMap:: (HFoldableAtk h) => (forall b. t b -> k b) -> h t b -> k b
Implemented in terms of this is
hfoldTraverse:: (HFoldableAtk h,HTraversableh,Applicativef) => (forall b. t b -> f (k b)) -> h t a -> f (k a)hfoldTraversef =fmap(hfoldMapid) .htraversef
This function will allow us to evaluate our expressions to ground values:
>>>hfoldTraverse evalXOrY exampleExprJust (Identity 7)
>>>hfoldTraverse evalXOrY exampleExpr2Nothing
Evaluating Variables Symbolically
With the help of sbv, we can evaluate expressions symbolically in order to
prove things about them.
Evaluating Expressions Symbolically
We need an instance of to do symbolic
evaluation, implemented like so:HFoldableAt SBV SimpleOp
instanceHFoldableAtSBVSimpleOpwherehfoldMap=implHfoldMap$ s -> case s ofAddx y -> x+yEqualx y -> x.==yIfThenElsex y z ->itex y zLiteralx ->literalx
evalSimpleExprSymbolic :: SimpleExpr a -> StateT (Map String (SBV Integer)) Symbolic (SBV a) Source #
Now we can evaluate expressions symbolically and use sbv to prove things about
them.
evalSimpleExprSymbolic =hfoldTraverseevalVarSymbolic
examplePredicate :: Predicate Source #
Let's ask sbv to prove that our expression always returns a value no less than
the variable y.
examplePredicate =flipevalStateTmempty$ do expr <-evalSimpleExprSymbolicexampleExpry <-evalSimpleExprSymbolic(var"y")return(expr.>=y)
>>>isTheorem examplePredicateTrue