Stateful Commands for the Mars Rover

Following up on the chapter "A mini DSL", I would like to include two new features in our DSL today:

  • command expressions like move rover 20 meters forward that read like plain english and

  • a sequence of such commands for the same rover

Step 1: Command Expressions

Command expressions make code read like plain english, which is helpful (or just cool) in many DSLs. They require that we get rid of all punctuation and this can be really difficult in many languages. Luckily, it is a piece of cake in Frege. Let’s find out how we could implement the following example:

A single command expression
move rover 20 meters forward

Now, this is just a simple move function with four arguments! Done.

Well, almost done. We need to find out what type rover should be and what the other arguments are. Here is a proposal: just make rover a Position with some nice record syntax and use Double for all remaining arguments. The return value is the new position.

Making the command expression work
data Position = Pos { x, y :: Double }
derive Show Position

rover   = Pos 0 0
meters  = 1.0
forward = 1.0

move :: Position -> Double -> Double -> Double -> Position
move position distance unit direction =
    position.{x <- (+ distance * unit * direction) }  -- ➊

This should all be familiar, where at ➊ we modify the x position in a fashion that we already encountered in "The Power of the Dot".

Homework I

The energetic reader may want to improve the solution by re-using the type-safe units of length that we defined in "Enhancing the DSL for Type Safety".

Ok, this works nicely, but as soon as we try to do multiple moves in sequence, the code becomes rather unwieldy. We would have to pass the updated position around such that the result of the last move becomes an argument to the next move.

pos1 = move rover ...
pos2 = move pos1  ...

It would all be so much easier if we could just modify the position…​

Step 2: Introducing State

Luckily, there is a State type in Frege and it perfectly fits our needs.

  • We can use a State Position type, meaning "state for a Position type".

  • State has a modify operation that takes a high-order function, which knows how to update a position. Ha, this will be our new move!

  • State also has an execState that runs our stateful computations (doing the moves) starting from an initial position. Perfect.

"bind" again

Running stateful computations technically means that we provide many move functions. These functions must be called in strict sequence and execState must bind the result position from one move function to the next.

When there is "bind" (in the form of the >>= operator) then we can also use the do-nation and we will do so in the final solution. Here it is.

Moving the rover by command expressions
module CommandExpressionRover where

import frege.control.monad.State

data Position = Pos { x, y :: Double }
derive Show Position

rover   = Pos 0 0
feet    = 0.305
meters  = 1.0
forward = 1.0
back    = -1.0

moveDouble -> Double -> Double -> State Position ()  -- ➊
move distance unit direction = State.modify update where
    update pos = pos.{x <- (+ distance * unit * direction) }

with start f = State.execState f start   -- ➋

main = do
    endPosition = with rover do
        move 20 meters forward           -- ➌
        move 10 feet back
    println $ "moved the rover around, ending at " ++ show endPosition

This code prints

moved the rover around, ending at Pos 16.95 0.0

A few remarks about the code where it is not entirely obvious:

At ➊ we see in the type signature State Position (). Why the "()" unit? Well, in full generality, State has not only one type parameter but two: State s a, where s is the type of the managed state - in our case Position - and a is the type that stateful computations can evaluate to. We make no use of a in our solution since we are only interested in the state change. If you ever use runState or evalState you will see the differences.

➋ introduced the small convenience function with to flip the parameter positions of execState just because that makes the do-notation easier to use. I think it also reads nicer. There is a flip function to do this in-place but I found it distracting.

At ➌ we see that rover has gone! It is now in the state. This is even more compelling as a DSL! And this works 100% typesafe without implicit delegates (this is for the language geeks).

Homework II

Add functions to change the direction of the next move, e.g. turn 90 degrees. For the geeks: check out FregeFX and paint the moves on a canvas.

Stateless State

Many people claim that using state in a purely functional language is hard - and I concede that it takes a bit more consideration than just writing position.x += distance * unit * direction. However, the code above is reasonably legible.

On the other hand: isn’t state undermining our purity goals? Have we silently converted to the dark side? No!

Staying pure

Even though the code above looks like it would operate on mutable state, that really isn’t the case. State transfers immutable values from one function to the next but never ever mutates any state! Using State is still purely functional.

You may also have recognized that we got the plain endPosition without the State context as a result value. This is only possible because State is pure.

References

results matching ""

    No results matching ""