1
1
mirror of https://github.com/thma/LtuPatternFactory.git synced 2024-12-04 12:43:14 +03:00

starting interpreter example

This commit is contained in:
thma 2018-12-29 13:07:59 +01:00
parent e9ccdc0d14
commit 0f3d6f1dbe
2 changed files with 94 additions and 38 deletions

View File

@ -30,6 +30,7 @@ I think this kind of exposition could be helpful if you are either:
* [Singleton → Applicative](#singleton--applicative)
* [Pipeline → Monad](#pipeline--monad)
* [NullObject → Maybe Monad](#nullobject--maybe-monad)
* [Interpreter → Reader Monad](#interpreter--reader-monad)
* [Composite → SemiGroup -> Monoid](#composite--semigroup--monoid)
* [Visitor → Foldable](#visitor--foldable)
* [Iterator → Traversable](#iterator--traversable)
@ -711,9 +712,78 @@ There are many predefined Monads available in the Haskell curated libraries and
[Sourcecode for this section](https://github.com/thma/LtuPatternFactory/blob/master/src/NullObject.hs)
<!--
#### TBD: Reimplementing the Evaluator with Reader-Monad
-->
### Interpreter → Reader Monad
In the section [Singleton → Applicative](#singleton--applicative) we have already written a simple expression evaluator. From that section it should be obvious how easy the definition of evaluators and interpreters is in functional programming languages.
The main ingredients are:
* Algebraic Data Types (ADT) used to define the expression data type which is to be evaluated
* An evaluator function that uses pattern matching on the expression ADT
* 'implicit' threading of an environment
In the section on Singleton we have seen that some kind of 'implicit' threading of the environment can be already achieved with `Applicative Functors.
We still had the environment as an explicit parameter of the eval function:
```haskell
eval :: Num e => Exp e -> Env e -> e
```
but we could omit it in the pattern matching equations:
```haskell
eval (Var x) = fetch x
eval (Val i) = pure i
eval (Add p q) = pure (+) <*> eval p <*> eval q
eval (Mul p q) = pure (*) <*> eval p <*> eval q
```
By using Monads the handling of the environment can be made even more implicit.
I'll demonstrate this with a slightly extended version of the evaluator. In the first step we extend the expression syntax to also provide let expressions and generic support for binary operators:
```haskell
-- | a simple expression ADT
data Exp a =
Var String -- a variable to be looked up
| BinOp (BinOperator a) (Exp a) (Exp a) -- a binary operator applied on two expressions
| Let String (Exp a) (Exp a) -- a let expression
| Val a -- an atomic value
-- | a binary operator type
type BinOperator a = a -> a -> a
-- | the environment is just a list of mappings from variable names to values
type Env a = [(String, a)]
```
With this data type we can encode expressions like:
```haskell
let x = 4+5)
in 2*x
```
as:
```haskell
Let "x" (BinOp (+) (Val 4) (Val 5))
(BinOp (*) (Val 2) (Var "x"))
```
In order to evaluate such expression we must be able to modify the environment at runtime to create a binding for the variable `x` which will be referred to in the `in` part of the expression.
Next we define an evaluator function that pattern matches the above expression ADT:
```haskell
eval :: MonadReader (Env a) m => Exp a -> m a
eval (Var x) = asks (fetch x)
eval (Val i) = return i
eval (BinOp op e1 e2) = liftM2 op (eval e1) (eval e2)
eval (Let x e1 e2) = eval e1 >>= \v -> local ((x,v):) (eval e2)
```
[to be continued]
### Composite → SemiGroup → Monoid

View File

@ -5,43 +5,31 @@ import Control.Monad.State
data Exp a =
Var String
| Op (Operator a) (Exp a) (Exp a)
| BinOp (BinOperator a) (Exp a) (Exp a)
| Let String (Exp a) (Exp a)
| Val a
type Operator a = a -> a -> a
type BinOperator a = a -> a -> a
type Env a = [(String, a)]
-- using a Reader Monad to thread the environment. The Environment can be accessed by ask.
--eval :: Real a => Exp a -> Env a -> a
eval (Var x) = asks (fetch x)
eval (Val i) = return i
eval (Op op e1 e2) = liftM2 op (eval e1) (eval e2)
eval (Let x e1 e2) = asks (eval e1) >>= \v -> local ((x,v):) (eval e2)
-- using a Reader Monad to thread the environment. The Environment can be accessed by ask and asks.
--eval :: Exp a => Exp a -> Env a -> a
eval :: MonadReader (Env a) m => Exp a -> m a
eval (Var x) = asks (fetch x)
eval (Val i) = return i
eval (BinOp op e1 e2) = liftM2 op (eval e1) (eval e2)
eval (Let x e1 e2) = eval e1 >>= \v -> local ((x,v):) (eval e2)
{--
env <- ask
v <- eval e1
local ((x,v):) (eval e2)
--}
-- using a State Monad to thread the environment. The Environment can be accessed by get, gets, modify.
eval1 :: (MonadState (Env a) m) => Exp a -> m a
eval1 (Var x) = gets (fetch x)
eval1 (Val i) = return i
eval1 (BinOp op e1 e2) = liftM2 op (eval1 e1) (eval1 e2)
eval1 (Let x e1 e2) = eval1 e1 >>= \v -> modify ((x,v):) >> eval1 e2
{--
--eval5 :: Num a => Exp a -> Env a -> a
eval5 :: (MonadState (Env a) m, Num a) => Exp a -> m a
eval5 (Var x) =
gets (fetch x)
eval5 (Def k v) = do
env <- get
put ((k,v):env)
return v
eval5 (Val i) = return i
eval5 (Add p q) = liftM2 (+) (eval5 p) (eval5 q)
eval5 (Mul p q) = liftM2 (*) (eval5 p) (eval5 q)
--}
-- simple environment lookup
-- environment lookup
fetch :: String -> Env a -> a
fetch x [] = error $ "variable " ++ x ++ " is not defined"
fetch x ((y,v):ys)
@ -53,15 +41,13 @@ interpreterDemo = do
putStrLn "Interpreter -> Reader Monad + ADTs + pattern matching"
let exp = Let "x"
(Let "y"
(Op (+) (Val 5) (Val 6))
(Op (/) (Var "y") (Val 5)))
(Op (*) (Val 3) (Var "x"))
(BinOp (+) (Val 5) (Val 7))
(BinOp (/) (Var "y") (Val 6)))
(BinOp (*) (Var "pi") (Var "x"))
env = [("pi", pi)]
print $ eval exp env
--print $ runReader (eval exp) env
--print $ eval5 exp env
print $ runReader (eval exp) env
--print $ eval5 exp (put env :: State (Env Double) ())
print $ evalState (eval1 exp) env
putStrLn ""