Haskell Tutorial IV : If This is A Case of Functional Programming, Then I am Switching to Lazy Evaluation

Part III: How Haskell Monads are Like a Muppet       Part: V   (coming soon) >

Finally, with the forth part of the tutorial, I get to describe something that will make my PHP-coding reader want to use Haskell:  lazy evaluation.

But first, let’s go back to two different ways I’ve approached the problem of evaluating a keyboard input from a user in our rogue-like RPG game.    In tutorial #2, I created a function called move that used guards to discern what action to take based on a given input.    Here is move:

move :: Char -> String
move a
    |  a == 'w'       = "UP!"
    |  a == 's'       = "DOWN!"
    |  a == 'a'       = "LEFT!"
    |  a == 'd'       = "RIGHT!"
    |  otherwise      = "HANG AROUND AND DO NOTHING!"

In tutorial #3 I switched to a series of if / else if / else statements.

forever (do
    char <- getChar
    if char == 'q' then putStrLn "Thanks for Playing!"
         else if char == 'w' then up y >> output x >> output y
         else if char == 's' then down y >> output x >> output y
         else if char == 'a' then down x >> output x >> output y
         else if char == 'd' then up x >> output x >> output y
         else putStrLn "you did not type a correct entry")

This version had the additional side effect of requiring a manual escape of the compiler program in order to quit (something that is easily fixable, but still).    Now I have a new function ‘dashboard’ that uses lazy evaluation to compute values based on a given input.   Without beating around the bush, here it is:

dashboard :: Char -> MVar Int -> MVar Int -> IO ()
dashboard 'q' _ _ = exit "Thanks for Playing!"    // stop the program
dashboard 'w' x y = up y >> putStr " Coordinate: ( x = " >> output x >> putStr " , y = " >> output y >> putStr ")."
dashboard 's' x y = down y >> putStr " Coordinate: ( x = " >> output x >> putStr ", y = " >> output y >> putStr ")."
dashboard 'a' x y = down x >> putStr " Coordinate: ( x = " >> output x >> putStr ", y = " >> output y >> putStr ")."
dashboard 'd' x y = up x >> putStr " Coordinate:  ( x = " >> output x >> putStr ", y = " >> output y >> putStr ")."
dashboard w x y = putStr " Coordinate: ( x = " >> output x >> putStr ", y = " >> output y >> putStr ")."   // keep the Coordinate values the same

So, let’s think about our problem for a second.    So far, we have been focussed on getting our character to move around a map.    But what if we want to do something completely different, like say show an inventory list if the user hits ‘i’?    Or maybe cast a spell?     We could have a bunch of if – then statements or a whole bunch of guards, but it all could end up being a mess and impossible to read.

Fortunately, Haskell let’s you specify particular actions based on the constructors (entry values) for your function.    In the case above, dashboard accepts 3 values — one CHAR and two MVar Ints (ie. a mutable variable that has to be an integer — see the previous tutorial).   If the dashboard receives a ‘q’ for a value, it will say ‘thanks for playing’ and quit the program (the two ‘_’s mean it doesn’t matter what the values are that follow it).   The next entries do what we’ve come to expect -> change the value of the MVar based on the movement and then output the current coordinate’s status.    The last statement is a catch-all, asking Haskell to output the current spot.

Why might this way of doing things be better than using (say) guards?    Well, the first reason is that it gets to the point.    if the user hits ‘q’, Haskell will not attempt to evaluate any of the other function calls.   Admittedly, this function isn’t really helped performance-wise by lazy evaluation -> but what if moving in a certain direction (in the spot where a monster was) involved a long series of recursive computations.    We would not want Haskell to be using up its resources trying to figure out what it’s supposed to do when all the user wants to do is quit.

However, there’s still alot that could be added here.   What if we want to do something when the user hits ‘s’ and y is equal to zero.    Then we could do something like this:

dashboard 's' 0 y putStrLn "You are blocked and can go no further."  >> putStr " Coordinate:  ( x = " >> output x >> putStr ", y = " >> output y >> putStr ")."

Again, this can have really great performance benefits for your program.    If dashboard gets an ‘s’ and a ‘0’, it will look no further and do the easy job of outputting the coordinate as is, rather than attempting to go through all kinds of irrelevant processes.   UPDATE:  Well, actually, this is all wrong.    Because ‘0’ is not an MVAR.    Mind you, there is a way to get the function to accept two Ints instead and convert the MVAR to an Int for our purposes.    That’ll be another tutorial though.

The last benefit is that it gives a nice human-readable understanding of what happens when the function receives what, without a whole range of complex nested elements.

The downside, of course, is that because Haskell is type-safe, you must give this function a CHAR (not a Maybe Char, or a String) and two MVAR Ints (not an MVAR String or ordinary Int) or you will get a type error.       Things could also get more confusing if you decide that you want to return an IO (String) or other value instead of just outputting something to the screen.    But handling that problem might be for another tutorial.

I should, again, remind you that my up-to-date work on the rogue-like game called rasghiosse is available in a patch-tag repository.   Please feel free to add to it as you wish.

Advertisements

Part III: How Haskell Monads are Like a Muppet

< Part II: How not to Start Your Haskell Program Part: IV:  If This is a Case of Functional Programming, Then I Am Switching to Lazy Evaluation >

In previous tutorials, I talked about some assumptions about Haskell programming, and how to overcome them.    I suggested that someone coming from an imperative background (more than likely) is going to go about functional programming all wrong.     I fretted that no one creating documentation for Haskell seemed to notice this and that this was a problem for the future of Haskell and functional programming in general.

Non-academic coders want to make their programs *do* something as soon as they can.      Academic programmers are more fascinated by making their code solve complex problems.     Making Haskell do something is possible, but you’d never know it by reading through the documentation and most tutorials.    Here I will make our Haskell program actually do something, all the while giving an inkling about how state monads / MVars / IORefs work (without ever bothering to explain the math-ese behind them).    Again, I am assuming you’ve read and have been confused by at least one or two tutorials on the subject.

Because imperative programs rely on mutable variables (of which functional programming has none), programmers usually start by creating controls mechanisms (eg. an array in php) to prevent mean side effects from creeping in to their software.    Functional programming does not have side effects.    That’s good.   However, it also does not have the ability to store values inside a variable.    That’s bad.   That’s why I suggested starting with input and output and filling in the gaps.   Then I gave a sample:


main :: IO() main = do
                        x <- getChar
                        if x == 'q'  then putStrLn "Thanks for Playing"
                            else pmove x >> main  

move :: Char -> String
move a
    |  a == 'w'       = "UP!"
    |  a == 's'       = "DOWN!"
    |  a == 'a'       = "LEFT!"
    |  a == 'd'       = "RIGHT!"
    |  otherwise      = "HANG AROUND AND DO NOTHING!"  

pmove = print . move

So far, this program doesn’t do much more than a basic “Hello World!” program.     It accepts an input that get stored in x and based on that input it prints out a word.   Pretty disappointing.   What we want is the ability to change things.    What would be really nice is graph coordinates indicating a plot on a map, for instance.    Then we could establish our character’s position that could be displayed graphically and used to analyze the characters position against (say) a wall, or a monster.   Then the real fighting can begin.

Except you can’t really do that.    If you create a variable ‘place’ that contains a tuple (5, 5), it will always be (5, 5).   Your character is stuck in the quicksand!

You *can* create a function ‘up’ that will increase the ‘y’ value when your user presses ‘w’.    Maybe it’s something like this:

up :: (Int, Int) -> (Int, Int)

up x = (fst x, (snd x) + 1)

However, you are still stuck because you have no declaration of a starting point unless you declare a variable.    If you declare a variable, then your starting point is unchangeable, so you can only move one spot and no more!      This is where you start questioning if Haskell is just some practical joke that some prof wanted to play on his or her students.   In academic terms, this is the ‘problem of state’ and is one of the main reasons why functional programming has not had much influence in its first 30 years of being in existence.

To avoid a surprise ending, the solution is actually to create an MVar, which means ‘mutable variable’ and can be found in the Control.Concurrent.MVar module.    But I am getting ahead of myself.   An MVar isn’t exactly a mutable variable, although it does a really good job at pretending it is one.   Let me use some of my mad working-with-kids librarian skillz to show you how it works.

Grover is a Monad

Since you are somewhat familiar with the Haskell tutorials, you know what a monad is.    You are also a little bit confused at how it works.   I am not going to explain it.   It’s too mathy.    Much better to talk in Muppets.     I like to read a book called Grover’s own alphabet to my kids.     In it, Grover moves his arms and legs around to shape himself into all of the letters of the alphabet.     Believe it or not, this is your solution to the mutable variable problem.

As discussed, I cannot create a function that will store a value in a variable that can be changed later.    However, I can ask a function to return a type.    I can call this type “Grover.”     So, if I’m looking for a letter of the alphabet, I can ask Grover to move his arms and legs so that he is in the shape of a letter, let’s say “G.”    He will stay in “G” until I ask what shape he’s in.     Once he says “I am in shape G”, then he will stand up straight (no shape) until I ask him to take another shape.   This is basically how Monads (more specifically, state monads) work.

(Incidentally, the idea of using Monsters as an example of the Monad type is not new.)

Now is the hard part – going through the documentation.     I am going to save you alot of time.   For our Rogue-like game, we need MVars.    Before figuring this out, I had to go through a whole lot of confusing reading and mess.     If you want some pain and suffering, look up the State Monad, State Transformers, IORef, Arrow notation and the like.    Now look at what you really need.   MVars.    Go ahead and read the documentation on that.   You’ll find out that MVars are used for supporting concurrent processes.    Then you’ll go back to IORefs because this isn’t a concurrent program.    That’s a pointless exercise – IORefs are unnecessary.    And, hey – maybe our Rogue-like game will need to be an MMORPG down the line, who knows?

Here is the code.      I will go through it in more detail in the next tutorial later, but basically this program will start with x = 0 & y = 0, and output a location or a message depending on what you hit on the keyboard.     Note, however, at this stage that you’ll need to hit ctrl-z to make the program stop.



import Control.Concurrent.MVar
import Control.Monad

main = do
           x <- newEmptyMVar
           y <- newEmptyMVar
           putMVar y 0
           putMVar x 0
           forever (do
                    char <- getChar
                    if char == 'q' then putStrLn "Thanks for Playing!"
                        else if char == 'w' then up y >> output x >> output y
                        else if char == 's' then down y >> output x >> output y
                        else if char == 'a' then down x >> output x >> output y
                        else if char == 'd' then up x >> output x >> output y
                        else putStrLn "you did not type a correct entry")

up, down, output :: MVar Int -> IO()
up y =  do
             v <- takeMVar y
             putMVar y (v + 1)

down y = do
             v <- takeMVar y
             putMVar y (v - 1)

output y = do
             g <- takeMVar y
             putMVar y g
             putStrLn (show g)


It’s not the prettiest code in the world, but that’s how you have to do it with Haskell.   Start out ugly, and make it pretty as you go along.