= Evaluator Rehaul
This chapter is Autrijus Tang's fault, although, Amir Livne Bar-On is partly
slightly guilty. These two evil persons invaded my peace and forced monads onto
my evaluator.
Amir just suggested I looked into it, but Autrijus didn't stop there. He
submitted a patch to Harrorth, our own monad, made from an `IO` and a `Reader`
using `Reader T`, IO. What a sadist, eh?
It's going to be abstract, and fuzzy, so I apologize in advance.
Oh my... Let's get started.
== The Reader Monad
The reader monad allows us to change the type signature of our `interpret`
function, so that it doesn't care about `Interp`. Here's the old definition:
interp :: Interp -> Forth -> IO Interp
Take an interpreter, and some Forth, and return an interpreter in the `IO` monad,
presumably after the application of the Forth AST.
This is the new signature:
interp :: Forth -> Eval Interp
The `Eval` monad is a new monad we'll be defining in a while. It's the
composition of the `Reader` monad and the `IO` monad. If `IO` was not involved
it could look like this:
interp :: Forth -> Reader Interp Interp
That would have been the type for a function interpreting (or rather, in this
sense defining) the combination of an interpreter's state and an AST into an
equivalent modified state, and empty AST (which is incidentally omitted).
Lets pretend for a while that the the `IO` monad is in fact really out of the
picture.
So when you call it:
interpret [Push 1, Invoke "."]
What do you get? A `Reader Interp Interp`, or a in English a `Reader`
computation that has an `Interp` and yields an `Interp`.
newtype Reader e a = Reader { runReader :: (e -> a) }
This Reader thing is then invoked like this
(runReader interpret [Push 1, Invoke "."]) someInterp
So what is `runReader`? It's type is:
runReader :: Reader r a -> r -> a
That is, runReader takes a reader, and it returns a function. `runReader` on a
`Reader Interp Interp` returns the function `(e -> a)` where both `e` and `a`
are `Interp`, hence `runReader :: Reader Interp Interp -> Interp -> Interp`.
== Divergence into Auto-currying
If you read it so that `runReader` takes a Reader, and an `r`, and returns an
`a`, well, that's the same thing. Lets take `(+)`:
Prelude> :t (+)
(+) :: (Num a) => a -> a -> a
`(+)` takes two parameters, both of a type that is in class `Num`, and returns
the same type. Let's look at the type of `(1 +)`.
It takes the function `(+)`
and passes it a `1`, and gets back a function which takes thing that is in
class `Num`, and then returns that same type.
Prelude> :t (1 +)
(1 +) :: (Num a) => a -> a
What's the type of `(1 + 1)`? Here it is:
Prelude> :t (1 + 1)
(1 + 1) :: (Num a) => a
It's a function that takes no parameters, and returns a num. That's also the
type of 2, by the way:
Prelude> :t 2
2 :: (Num t) => t
So what's going on? 2 is a function? Yes!
Functions don't really take 2 parameters? Yes!
`(+)` is a function that takes a number, whose evaluation results in a
function that takes another number, which evaluates to the sum
Haskell's syntax just lets you ignore the fact that it does that. This sheds
light on a lot of constructs:
addA = 1 +
addB n = 1 + n
The difference between the two is that the first one says "`addA` is a name
for the function resulting from applying + to 1".
The second function says "addB is the function \n -> (1 +) n", that is, "`addB`
is a name for the function that takes a single parameter, n, and then applies
the function resulting from applying + to 1, to n".
Pretty cool, eh?
== Back to Reader
So if we return to `interpret`, we can say that it encapsulates a function, in
a `Reader`. Since `runReader` extracts the value (our function) out of the
`Reader`, the expression
(runReader (interpret ast))
evaluates to a function, and it's type signature is
Interp -> Interp
If this is too much theory please stop, back up, and read through it again.
It's all really some seemingly pointless ping pong. If this is too little
purpose, read on.
The way the function that takes an Interp and returns an Interp is constructed
is due to the definition of the instance of `>>=` for the `Reader` type. We'll
look at that shortly.
What this function does is more important at the moment.
Given the function `initInterp`, which creates an empty interpreter for us,
lets see what this expression does:
(runReader (interpret ast)) initInterp
It takes the function which wants an `Interp` and returns an `Interp`, and
gives it an `Interp`.
Under the surface the function whose type is `Interp -> Interp` could first be
seen as something like this:
\i -> ((runReader (doExp someExp)) i)
And `interpret` looks like this:
interpret ast = return $ \i -> ((runReader (doExp someExp)) i)
Lets say that `someExp` is a bit of the AST. As many famous movie bad guys
said, "details, details.".
`doExp`'s signature is
doExp :: Exp -> Reader Interp (Interp -> Interp)
So `doExp` also returns a function, that takes an interpreter, and does something to it. Lets invent a simplified `doExp`:
doExp _ = return $ \interp -> interp{ stack = [] }
This `doExp` uses `return` to create a `Reader` which contains a function that
will return an `Interp` with an empty stack.
`interpret`'s function takes `i`, and then unwraps `doExp`'s function using
`runReader`, and sends it the interpreter it got.
What do we get out of this? nothing more than a mess right now... But you may
have noticed that the `AST` and the `Interp` are passed independently of each
other.
Remember how the `IO` monad passed the world around implicitly? This is the
beginnings of what we're going to do with our `Interp`. `interpret` is like a
function with side effects on an `Interp`, a sort of "world of a forth
program", so we're making sure each invocation gets an `Interp`. We were doing
that before, though, so what's the big deal?
Well, the elegance of the `IO` monad is that you can just assume the world is
being passed around, since it always needs to be.
The `Reader` monad lets us do it with our `Interp`. How? Using it's definition
of `>>=`!
== Enter `>>=`
One of the beautiful aspects of monads in Haskell is that do notation is so
generalized, basically being readable shorthand for something pretty
complicated.
Lets look at what a typical `>>=` does.
It takes the value given to it on it's left, and then, applying some logic
which is "in the monad", eventually shoves the value into the function on it's
right.
In the `IO` monad `>>=` uses another monad, `State` to retrieve the world, and
shoves that into the function on it's right.
In the `Reader` monad, `>>=` takes the `Reader` on it's right, like one
returned from a `doExp`, and then does this magic:
(Reader r) >>= f = Reader $ \e -> (runReader (f (r e))) e
It extracts the function that `doExp` returns, `r`, and takes the function on
it's right.
Then it returns a function that takes a parameter called `e`. `e` is an
`Interp`, by the way. The function returned by `>>=` is actually the function
returned by by `interpret`.
Lets see exactly what the returned function does:
(r e)
It applies the `doExp` function to the `Interp` it gets. In this case, the
stack clearing function (inside `r`) is applied to the empty `Interp` (in `e`).
This returns an `Interp`, since the type of `r` is:
r :: Interp -> Interp
Then it applies the other parameter to `>>=`, `f`, or the right hand side, to
the modified `Interp`. For example, lets take
interpret ast = doExp (head ast) >>= \i -> return i
Written in do notation as
interpret ast = do
i <- doExp $ head ast
return i
So `f` is the function `\i -> return i`. Or basically a function that takes a
parameter, and puts it in the `Reader` monad. Here's Reader's `return`:
return a = Reader $ \e -> a
So `return` takes a value, in this case `i`, the `Interp` returned by applying
`doExp`'s function to the initial `Interp`, and it builds a `Reader` which
creates a function that takes a single parameter, and just throws it away,
returning the `i` that `return` was given.
So we evaluated `f (r e)`, and we got a function that takes an `Interp` and
returns a `Reader` that contains another function that will take an `Interp`,
throw it away, and return the `Interp` passed into `f`.
We take this function apart using `runReader`, and get the function returned by
`return`, namely
\_ -> someInterp
and then we apply it to `e`. `e` is thrown away, the modified interpreter is
returned.
So let's back up now. The function that does all this is put in a `Reader`, and
sent back. This is the value of `interpret ast`.
When we take *that* apart with `runReader`, and apply the function to
`initInterp`, it all reduces, and presto, stuff happens.
Please measure the temperature of your brain, by assertively introducing a
thermometer to your ear canal. If it's too hot, take a break, you deserve it.
If it's around 37°c then you are much smarter than me, and you are welcome to
carry on.
== Real examples
Our `interpret` function does more than just evaluate a single expression. It
is recursive. Let's first look at the condition for an empty AST, the end of
the recursion:
interpret [] = ask
And some definitions from the `Reader` monad and the `Prelude`:
ask = Reader id
id x = x
Or in a single line:
interpret [] = Reader $ \x -> x
So basically the empty AST will be `interpret`ed into a Reader that contains a
function that returns whatever is fed into it.
interpret (exp:exps) = do
fun <- doExp exp
local fun (interpret exps)
local f c = Reader $ \e -> runReader c (f e)
This is the general `interpret` function, that reduces a single expression in
the AST. It takes the function that will change the `Interp`, as returned by
`doExp` and names it `fun`, and then sends `fun` into the `local` function.
`local` will return a function that takes an `Interp`, and a `Reader`. The
`Reader` is provided by evaluating `interpret exps`, that is `interpret` on the
remainder of the AST, after removing the `exp` we are `doExp`ing now. The
function returned by `local` will apply the function returned by `doExp` on the
`Interp` it gets, and then take the function returned by `interp exps`, and
give it the `Interp` as returned by the function that modified the interp, that
`doExp` provided.
I sound a bit like Mojo Jojo, don't I?
In effect, the recursive process creates functions which get `Interp`s, and
then apply functions provided by our monadic action, like `doExp` and
`interpret`, and to the `Interp`s they got.
In a way the `Reader` monad is just plumbing, hidden by do notation, which
allows us to pass an `Interp` around, without taking an `Interp` at the top
level.
Autrijus says this lets us have polymorphic evaluation code. I don't see that
yet, but when we start compiling to Parrot, I should.
== That Pesky `IO` Monad.
Autrijus's patch does something further to our interpretation code. Let's look
at what it does.
Naturally he imported
import Control.Monad.RWS
so that the `Reader` monad's definition is available. Now the real changes.
First, he changed the field names in `Interp`, and changed it's constructor
name too. I bet that's good style:
data Interp = MkInterp
{ interpStack :: Stack
, interpDict :: Dict
} deriving (Show)
Then he created a new type, a monad, called `Eval`
type Eval a = (MonadReader Interp m, MonadWriter Stack m) => m a
Which combines two other monads, a `Reader`, giving us the `ask` and `local`
functions, and a `Writer`, which can be treated sort of like a wrapper for `IO`
for now. I think we've had enough for one day, don't you? We'll look at it
later.
If you would like googleable details, `Eval` is transformed by the `ReaderT`
monad transformer, to compose `Reader` behaviors with `Writer` behaviors, and
encapsulate IO actions inside `Writer` actions.
Basically it provides a `tell` function which knows to print the `Stack` using
the `IO` monad.
interpret :: Forth -> Eval Interp
interpret [] = ask
interpret (exp:exps) = do
fun <- doExp exp
local fun (interpret exps)
We've seen these before, when we we're pretending `Eval` was just a `Reader`.
They are essentially unchanged.
doExp :: Exp -> Eval (Interp -> Interp)
doExp (Push lit) = doStack (lit:)
Here Autrijus uses a function, `doStack`, which applies transformations to the
stack. In this case, the function `(lit:)` is applied. Remember autocurrying?
`(:)`, the list constructor, takes an element, and a list. `(lit:)` returns a
function which needs to get a list, and will return a list wtih `lit` tacked
on. Here's `doStack`:
doStack :: (Stack -> Stack) -> Eval (Interp -> Interp)
doStack f = return $ \i -> i{ interpStack = f (interpStack i) }
As you can see it returns an `Eval` which contains a function that will apply
`f`, in this case `(lit:)` to the `interpStack` of `i`, the `Interp` that
`doStack`'s function will eventully get.
Here are the two reasons why a monad that be a `MonadWriter` that can write
`Stack`s is involved:
doExp (Invoke ".") = do
(x:xs) <- asks interpStack
tell [x]
return (\i -> i{ interpStack = xs })
doExp (Invoke ".S") = do
stack <- asks interpStack
tell stack
return id
These print the top of the stack, and the stack, respectively, remember? They
do so by using `tell`. What Autrijus did is say that `IO` can be a
`MonadWriter` for `Stack`s, and defined the functions to do so:
instance MonadWriter Stack IO where
tell = putStr . unlines . map (("==> " ++) . show)
listen = fail "Cannot listen"
pass = fail "Cannot pass"
When `dumpInterp` in `Harrorth.Shell` will interpret, it will cause the `Eval`
monad we created to match a monad that is both a `MonadReader` and a
`MonadWriter`. `ReaderT` is what's called a Monad Transformer, and its job is
to take the `IO` monad, which is a `MonadWriter`, and the `Reaeder` monad, a
`MonadReader`, and combine them in a monad that can do both.
I don't know how it does that yet, hopefully I will learn later.
The bottom line is that the `Eval` monad basically says that "an `Eval` monad
is any monad that can be a `MonadReader` for the `Interp` type, and a
`MonadWriter` for the `Stack` type". `ReaderT` knows to add `MonadWriter`
behavior to any monad, Autrijus's `MonadWriter` instance for `IO` does the
latter.
The `IO` monad can be both, because Autrijus's code makes it `MonadWriter`, and
`ReaderT` knows how to make it a `MonadReader`.
Another unfamiliar function in the stack printers is `asks`. Let's look at it
in more detail:
asks sel = ask >>= return . sel
Asks takes a function, and composes an `Eval` that will bind `ask`, shorthand
for `Reader (\x -> x)`, with `return . sel`. So basically, the `Interp` coming
into the `Eval` action created by `asks` will be passed right into the right
hand side of '>>=' because `ask` is basically a no-op in that respect.
`return . sel` is a funny one. Here's the definition of `(.)`:
(f . g) x = f (g x)
Or
(.) f g x -> f (g x)
Remember autocurry? The expression is morally equivelent to saying
asks sel = ask >>= \x -> return (sel x)
The `Interp` will be bound to `x`, which will be fed into `sel`. In our case
`sel` is `interpStack`, the function that extracts a stack from an `Interp`,
generated from it's type definition.
So the stack is wrapped in the eval, and then
stack <- asks interpStack
will extract the stack from `Interp`, and bind it to `stack`. (remember that do
notation just does `>>=`?).
In the "." word this stack is further pattern matched, and not only that, it
returns a function that returns an interpreter with only the remainder of the
stack in it.
Phew!
Let's do some more builtins. This one clears the stack:
doExp (Invoke "0SP") = doStack $ const []
`const` is defined as
const a _ = a
Autocurry strikes again!
const :: a -> b -> a
means that const is also definable as
const a = \_ -> a
Or a function that takes a parmeter, and returns a function that throws away
its paramter, and returns `const`'s original parameter.
`const []` is means `\_ -> []`. When sent to `doStack`, it will get a stack,
throw it away, and return an empty one instead.
You already know `DUP`:
doExp (Invoke "DUP") = doStack $ \stack -> (head stack):stack
`\stack -> (head stack):stack` is a function that takes a stack, and returns a
stack with the head of the stack, consed with the stack, that is, the stack
with it's first element tacked onto it again, or in effect duplicated.
Here's another nice example of currying:
doExp (Invoke "DROP") = doStack $ tail
`tail` is a function that takes a list, and returns it without the first
element. Just exchange "list" with "stack" in that sentance (or let type
inferrence do it for you), and that makes sense too!
You should be able to figure `SWAP` out yourself:
doExp (Invoke "SWAP") = doStack $ \(a:b:xs) -> b:a:xs
`NewWord` and `Invoke` on user words are similar to what they were before.
`NewWord` returns a function in the `Eval` monad, that will manipulate the
dictionary of an `Interp` to contain the new word:
doExp (NewWord word body) = return $ \i -> i{ interpDict = insert word body (interpDict i) }
And user word invocation `asks` for the dict, `interprets` the AST in the value
of the entry in the `dict` keyed by `userWord` (the word's body), then extracts
the `Interp` out of that.
Then it will use `const` to construct a function that simply returns the
`Interp` created by `interpret`ing the `userWord`'s AST:
doExp (Invoke userWord) = do
dict <- asks interpDict
i' <- interpret (dict ! userWord)
return $ const i'
Autrijus also made some minor changes to `Harrorth.Shell`:
First he modified and adds some imports:
import Data.Map (empty)
import Data.Char (toUpper)
import Control.Monad.Reader
Then he changed `main` to convert the forth source code into uppercase. I would
actually prefer a case sensitive forth interpreter, but what the hell, it's
easier to type `1 2 dup swap` than `1 2 DUP SWAP`:
main = do
src <- fmap (map toUpper) getLine -- see how toUpper is used char by char?
case (parse forthProgram "" src) of
Left err -> do
putStr "parse error at "
print err
Right x -> dumpInterp x
He also adds a function that evaluates to an empty `Interp`:
initInterp :: Interp
initInterp = MkInterp
{ interpStack = []
, interpDict = empty
}
And lastly this `IO` action composes the `IO` action with the the `ReaderT`
monad transformer, to create the `Eval` monad. It takes the `Eval` from
`interpret ast`, applies `runReaderT` to it, and then hands the resulting
function to an `initInterp`.
The resulting `Interp` is bound to `finished`, and then printed.
dumpInterp :: Forth -> IO ()
dumpInterp ast = do
finished <- (`runReaderT` initInterp) $ interpret ast
print finished
The funny syntax:
`runReaderT`
Takes `runReaderT`, and makes it into an infix function. That line could have
been written as
(\x -> (runReaderT x) initInterp) interpret ast
because it autocorries the runReaderT's second parameter (well, it doesn't, it
does exactly what my explanation line does - creates a function that takes a
parameter, applies `runReaderT` to it, and then applies the result to
`initInterp`).
The `IO` monad enters the picture because of `dumpInterp`. `runReaderT` within
the `dumpInterp` action gives the `IO` monad `MonadReader` capabilities, and the resulting monad can be matched by type `Eval`.
That's it!
If you feel like reducing the some expressions in more detail,
`misc/reader_reductions.txt` has two examples. I doubt they're very readable on
their own, but if you're doing your own reduction and you compare as you go
along it might help.