Perl, Haskell, stuff
After the last post, we have parser actions that can recognise an integer or an item of merchandise. Now we need to be able to process a command, like “jet bronx” or “buy 4 lambdas”. Let's start off with this basis:
> parseCommand = parseMap commandMap
> commandMap = getPrefixMap [
> ( "buy", cmdBuy ),
> ( "sell", cmdSell ),
> ( "jet", cmdJet ),
> ( "quit", cmdQuit )
> ]
Now, what we roughly want to do is:
We might come up with something like this:
> parseLine s = do let (cmd:pars) = tokenizeLine s
> c <- parseCommand cmd
> c' <- c pars
> return c'
But I'd promised that we'd check if the parsing worked! Can you see any checks like that above?
If we check the type of parseCommand, we'll notice it returns a Maybe
parseCommand :: [Char]
-> Maybe ([String] -> Maybe (GameState -> Maybe GameState))
This means that the do expression starts with a Maybe (we don't count the let expression) and so is in the Maybe monad. If any of the sequence fails, parseLine will return Nothing, without us having to specify anything! This is quite cute and, once you get used to it, rather intuitive (the definition above fell naturally out of my text editor).
Here's another example of Maybe - parsing the expressions like “buy 4 curry” or “sell 10 stm”. First of all, we notice that cmdBuy and cmdSell both have the same form, so we'll share the code in a common parser called cmdMerchandise.
> cmdBuy = cmdMerchandise doBuy
> cmdSell = cmdMerchandise doSell
This parser looks at the first 2 parameters, and tries to parse them respectively as an Int or an item of Merchandise.
If either of the parses fails, it will magically return Nothing.
If it succeeds, it will return the result of, for example doBuy 10 lambdas. (The result is of course a function that takes a GameState in input, and returns a GameState that is the result of having bought 10 lambdas. Very meta.)
> cmdMerchandise f (n:m:_) = do n' <- parseInt n
> m' <- parseMerchandise m
> Just $ f n' m'
> cmdMerchandise _ _ = Nothing
This is getting a little abstract if we can't test it. Right now our GameState record doesn't have a “list of merchandise” structure, so let's keep it simple for the sake of argument and add a debug string instead.
> data GameState = GameState {
> turn :: Integer,
> score :: Integer,
> location :: Location,
> debug :: String
> } deriving Show
>
> type Location = Int
(and add a debug = ““ to the startState declaration.)
We'll make the doBuy and doSell functions just modify the debug string:
> doBuy n m gs = return gs {
> debug = "You bought " ++
> (show n) ++ " " ++
> (name m)
> }
> doSell n m gs = return gs {
> debug = "You sold " ++
> (show n) ++ " " ++
> (name m)
> }
OK, we could factor these out as an exercise, but we'll be replacing them soon. Now, what we really want to do is to test it! I'll look at plugging this into the prompt structure next time, for now let's just create a test function. This just takes a GameState and a line, and returns the new state if it all worked out.
> test gs s = do c <- parseLine s
> c gs
We can play with this to see if it worked:
*Main> test startState "sell 3 la"
Just (GameState {turn = 1, score = 0, location = 0,
debug = "You sold 3 Lambdas"})
*Main> test startState "panic"
Nothing
The other actions are similar. We'll use the record mutators modScore and nextTurn that we saw last time.
> -- Just a stub: We'll probably want to set an "endflag" or similar.
> cmdQuit _ = Just doQuit
> doQuit gs = return $ modScore (-10) gs {
> debug = "Quitter!" }
>
> cmdJet (n:_) = do n' <- parseInt n
> Just $ doJet n'
> cmdJet _ = Nothing
>
> doJet n gs | n == location gs
> = return $ gs {
> debug = "You are already in location "
> ++ show n }
> | otherwise
> -- Jetting increments the turn counter
> = return $ nextTurn gs {
> location = n,
> debug = "You have moved to "
> ++ show n }
And we can now test the remaining actions:
*Main> test startState "jet"
Nothing
*Main> test startState "jet 1"
Just (GameState {turn = 2, score = 0, location = 1,
debug = "You have moved to 1"})
*Main> test startState "jet 0"
Just (GameState {turn = 1, score = 0, location = 0,
debug = "You are already in location 0"})
*Main> test startState "qu"
Just (GameState {turn = 1, score = -10, location = 0,
debug = "Quitter!"})
Next time around, we'll plug these actions into our prompt, and we'll work on representing the game state
Osfameron's blog on Haskell, Perl programming, stuff.
Leave a reply