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.