Schwartzian transform in Haskell

18 Jun 2008 In: perl

In the last post, I mentioned that we might be able to improve the performance of our sort using a "http://en.wikipedia.org/wiki/Schwartzian_transform".

This basically involves precaching the expensive calculation (in this case, length), sorting by the cached value, then taking just the original values.

Let's test with a list of words:

words "If on a winter's night a traveller"

So instead of the original sortBy (flip $ comparing length), we'd have something like:

  map fst 
. sortBy (flip $ comparing snd)
. map (id &&& length)

Let's read it from the bottom. First of all we create a list of tuples using the rather wonderful &&& operator from Control.Arrow.

[("If",2),("on",2),("a",1),("winter's",8),("night",5),("a",1),("traveller",9)]

Then we sort comparing the new second field of this tuple.

[("traveller",9),("winter's",8),("night",5),("If",2),("on",2),("a",1),("a",1)]

Finally we map again, getting just the first element of the tuple.

["traveller","winter's","night","If","on","a","a"]

And we can easily abstract this with a new function

sortST cmp f = map fst
. sortBy (cmp snd)
. map (id &&& f)

Now we can write:

sortST       comparing  length listOfWords
sortST (flip.comparing) length listOfWords

This is very similar to the sortBy syntax, except that we've separated out the "comparing" from the "length" clause, in order to compose the two separately for the new transformation.

Will on #geekup has been working on a Countdown letters and numbers game solver written in Python. I thought it'd be fun to try to do it in Haskell, and started with the letters game (anagram) solver.

Starting with a string of jumbled letters, the goal is to make the longest possible anagram. I remember the first time I tried to solve anagrams I jumped into the problem without thinking and got mixed up in all kinds of complicated combinatorial mess. The actual answer is very simple: let's take two words which are anagrams of each other:

  • monad
  • nomad

Both of them contain the same letters, so they are identical in some form of "canonical representation", for example

  • {a:1, d:1, m:1, n:1, o:1} -- dictionary mapping letter to number of times used
  • "admno" -- a string with the letters sorted
So we just need to consider all the subsets of the original jumbled letters in turn, and compare them against a map of:: canonical representation -> [list of words].

So for example:

pmqnrdzoa
...
 m n d oa
...
This function is called a powerset. I'm lazy so I googled a definition. We want the longest words first. The definition of powerset I found does a depth first search so it's not in order of length. What we want to do is to work on a list like this
list s =
sortBy (flip $ comparing length) -- longest first
. nub -- unique entries only
. powerset -- all combinations of
. canonicalize -- canonical (sorted) string
$ s
where canonicalize is just sort . map toLower . filter isLetter.

comparing is a nice litle utility sub that makes the above effectively the same as (\a b -> length a `compare` length b). We then flip it to reverse the ordering (and this is actually a good use for flip ;-).

Ordering by length is potentially inefficient — it checks the length of each element twice, and unlike Perl (where a string knows its own length), a string is just a list, so it has to descend the list to find it out. This is easy to optimize by precalculating the lengths, using a technique that in Perl we call the "Schwartzian transform", and I'll probably come back to this.

OK, so we have a list of subsets to compare, now we need to find a dictionary of canonical representations of words. Luckily most unixy distributions ship with one, often /usr/dict/words, but Ubuntu sticks is elsewhere.

I asked on #haskell, and was told I should use a Data.Map, Haskell's basic equivalent of a hash or associative array, but implemented using a Functional Programming friendly tree representation. In actual fact, quicksilver, mrs, and mmorrow told me the answer straight away, but let's pretend for the purpose of this post that we're working it out now :-)

Assuming I load that module, as is common, as M, I'd essentially want to call M.insertWith (++) on each element. The (++) is the concatenation operator, and it's the right thing to use because the dictionary is mapping String -> [String], for example

fromList [("admno", ["nomad","monad","Damon"]),...]

insertWith returns a new copy of the Map each time. It's like an accumulator which gradually takes on the entries from the list of words. And whenever we think about accumulators, we can think about folds.
foldl' (\m x -> M.insertWith (++) (canonicalize x) [x] m) mempty listOfWords
mempty is shorthand here for "an empty Data.Map". But we can go one better as apparently fold/insertWith is so common that there is a shorthand, fromListWith!
fromListWith (++) . map (canonicalize &&& return)
Woah! That's quite compact, and I just introduced some new syntax too: The &&& is basically saying "let's make a tuple with the result of calling these 2 functions on my input!" so it's the same as
fromListWith (++) . map (\a -> (canonicalize a, return a))
And return just means "wrap this value in the appropriate Monad". So it's a scary way of saying [a], because we're "in" the List monad. (In the same way that mempty above was an empty Data.Map, because it was "in" the Map Monad.)

Whenever I play with Map, I get angry errors about the monomorphism restriction. The way around that is to add an explicit type signature. If, like me, you're not quite sure what to put there, you can add a compiler directive to quell the error, then work out what the signature would be by calling :t my_function from the GHCI command line. (You'll often find afterwards that you can remove the signatures if you wanted to, because later on the compiler has more information to work out the types of things. It's only really during incremental development that you get the problem.

{-# LANGUAGE NoMonomorphismRestriction #-}
-- (that's the compiler directive, you can comment this out later)

makeAnag s = do
d <- dict
return $ take 4 $ getAnagrams s d

dict = do file <- readFile "/etc/dictionaries-common/words"
return $ mkdict $ lines file

mkdict :: [String] -> M.Map String [String]
mkdict = M.fromListWith (++) . map (canonicalize &&& return) . filter longEnough

longEnough = (>=3) . length
As you can see, for all the perceived difficulty of doing IO in a pure language like Haskell, it doesn't seem all that hard in this simple case. readFile reads the file, and lines splits it into an array of lines.

The final thing is to check each powerset against the dictionary. To extract the value, we use M.lookup. This function fails if it can't find a value. So we could do

  • For each powerset in the list
  • Check if it's present
  • And add it to the list if so
Which of course we could do with the Maybe type and a filter. But we want a list, and in the List type, failure is represented by an empty list []. So we can just map and we'd get something like:
[ ["anagram"], [], [], ["anagram 1", "anagram 2"], [] ]
With an empty list for each failure. We can use concatMap to join these together. So it's:
concatMap (\v -> M.lookup v dict) listOfPowersets
Though that actually returns:
[ ["anagram"], ["anagram 1", "anagram 2"], ]
which I hadn't expected. (M.lookup returned a list like ["anagram 1", "anagram 2"]. Quite literally it returned it, which in List context means it actually passed [["anagram 1", "anagram 2"]], which is why the list isn't completely flattened by concatMap. I get around this by using join. This is another of those monadic functions: in List context it does exactly what we want here, flattening this list.
getAnagrams s d = join 
. concatMap (flip M.lookup $ d)
$ filter longEnough -- 3 or more letters
. sortBy (flip $ comparing length) -- longest first
. nub -- unique entries only
. powerset -- all combinations of
. canonicalize -- canonical (sorted) string
$ s
You can look at the final Haskell Countdown code. I'll look at optimizing the sort and the powersets soon, any comments on other improvements (including better algorithms) very welcome.

Monads in Perl (take 1)

13 Jun 2008 In: perl

I've been away for a while from Haskell so I thought I should do some revision and really get my head around Monads. While I plodded through the wonderful "meet the monads" tutorial, I decided that the best way to learn would be to do. By implementing Monads in Perl. I'd highly recommend trying to implement monads in Your Favourite Language, if it supports lambdas. Perl has already been done by Greg Buchholz and rather nicely too, but there's no Monad library on CPAN so I thought it would be worth a try.

First of all, the question of how to model "types" is easily resolved. We bless each monad into the Monad class or a subclass. These can then have methods for bind and return etc.

Now I do like the haskell >> and by a stroke of good fortune, Perl allows us to overload that symbol too.

use overload '>>'   => 'Bind';

I use the string 'Bind' rather than the reference \&Bind, so that the subclasses can easily override it.

Some default bind methods in Monad.pm and Monad::Maybe etc., available here and we have some simple examples like this one (in test.pl):

my $result = 
(Writer 2) >>
L { my $x = shift; (Writer $x*2, "Doubled. ") >>
L { my $y = shift; (Writer $y+1, "Plus 1. ") >>
L { my $z = shift; (Writer $z*3, "Tripled $z. ")
}}};

Woot! OK, that's not entirely beautiful, but it's been slightly improved by the overloading of >>.

The L lambda generator is also there for readability. It's basically defined as

sub L (&) { shift }

i.e. it's an identity function, but it's an L (like lambda) and to my mind, lined up on the left, it looks pleasingly like "and then".

Nests

This didn't just fall straight out of the text editor into fully working code, of course. A blow-by-blow account of me getting confused wouldn't be especially interesting, but one big "aha" moment is worth pointing out. I realised that I was thinking of monads as being a chain of lambdas, each one passing control to the next, like OO chaining:

Chain of lambdas?

But that doesn't work, as of course then the $x, $y, $z of each scope would be separate, whereas in fact, in "later" sections, you can refer to $x too. This implies that the model is more like a nest of lambdas:

Nest of lambdas

This is made fairly clear in the Perl above, with its delimited braces, if you look at where the closing "}" are, and which opening "{" they match up with.

This is an interesting mind shift, and one that I still haven't really fully grasped, as I'll demonstrate a bit later.

Polymorphic functions on monads

In Haskell, you can call "return" in a monadic block to "lift" a value to the appropriate monad. Similarly, you can call "fail", and the function will fail in the right way (returning Nothing in a Maybe, throwing an error in IO). This is a function call, not a method, so how does it know which monad to behave as?

Of course Haskell does this with its strong inferencing typechecker. The compiler "knows" that we are in Maybe, so "fail" will be fail :: Maybe.

Perl on the other hand doesn't have a strong type-inferencing compiler... Right now I'm doing some shonky magic with caller() that works in this very simple test case (and I believe only in this test case). I think I could just simplify things and set a dynamic variable "$Monad::current_monad" on the first occurrence of Bind. Yeah, global variables, yuck. The final alternative that occurs to me would be to run the whole thing in a Reader monad which just passes the name of the monad... but I'm fairly sure that's slightly insane.

So what can it do right now?

The test script shows the current capabilities. As of r246, I have Writer, Maybe, and List implemented (the Monad superclass is effectively Identity).

I think Maybe is very useful - with some wrapper functions that raise Perl functions to monadic ones using a variety of strategies (fail on undef/0/die etc.) it could be a useful addition to the toolbox, simplifying a nested set of if checks.

The List monad already does list comprehensions, albeit with a rather yucky syntax. Which is of course the big problem, 'cos Perl programmers (and this statement may surprise non Perl programmers :-) are often obsessive about syntax.

Making it look pretty

OK, so we already added a bit of sugar with the >> overloading, and the L function for lambda generators, but it's still rather ugly with the mix of Perlish argument unpacking (my $x = shift), scope delimiters (}}}) etc.

Source filters!

The original Perl monad tutorial used a source filter to give a monadic Do notation. It's a fairly nice one as they go, but I don't really want to treat my program as a string if I can help it, so let's look at some other techniques first!

Devel::Declare

Matt Trout has been working on some crazy parsing magic in Devel::Declare. This isn't a source filter, but (I think) hooks into Perl's parser to change the way that subroutine declarations are parsed. It'd designed to give us parameter unpacking, so that we could substitute:

L {my $x = shift; .... }

with:

L ($x) { .... }

In the current version this doesn't work (you can define L like that easily, but the overloaded >> evidences a minor parsing bug (you'd have to put the expression between parentheses to get the precedence right, which loses the syntactic advantage we gain).

Still, hopefully will be fixed in a future release.

Generators

"Valued Lessons" has a beautiful post on Monads in Python (with nice syntax!). The parenthesis is not hyperbole: the post describes a monadic do block which looks about as pretty as Haskell's, but which works in a different way. We spell 'bind' (Haskell's <-) as 'yield'. So a control sub calls the 'do' block, gets out monadic values one by one as they are yielded back, and deals with the nitty gritty of binding them to the rest of the generator.

It took quite a while to understand the Python code: in fact I'm not sure I understand it fully, I really don't buy into the "Python is so easy to read" meme, and certainly the "@whatever" syntax, which seems to be 'decorators' that modify the subroutine that follows them, are rather confusing at first. But it's quite impressive, and it took me a while to replicate in Perl.

First hurdle: Perl doesn't have generators. OK, that shouldn't be an issue, I thought, because we have the CPAN. And yes, I found Brock Wilcox's Coro::Generator.

This doesn't quite do what I want though. The yield only works one way, so

my $x = yield (Monad 3);

doesn't actually bind $x to 3. I asked Brock on IRC, and apparently this behaviour is desired (I'm not quite sure why) so I forked his code to play with it :-) Also, the coroutine restarts immediately it finishes, which is inconvenient. Brock suggested yielding undef at the end, which is fine, I can do that from the control sub. (The Python version deals with finishing by throwing an exception, so perhaps it has the same semantics?)

After a lot of ugly pain, I finally got this working, and we can now do:

my $result = Do {
my $x = yield (Just 3);
my $y = yield (Nothing);
my $z = yield (Just 5);
warn "x=$x, y=$y, z=$z";
Just 6;

Why the pain? Failing to understand coroutines while trying to use them to implement monads (which I understand only very slightly) was a bad start. I found myself using the Do function to repeatedly take a value from the generator and bind it with the next value (rather than letting the monadic bind deal with those details). And even when I'd realised that the sub that I needed to bind was a lambda that would abstract the details of invoking the coroutine, I still ended up flailing around more or less at random till I finally got it working.

The current code is ugly (declared inline in test.pl rather than modularized) but the result is pleasantly magical and readable.

Props of course to Python for having powerful techniques like yield and decorators in core!

Hold the champagne

Of course the final test example, in the List monad doesn't work. Why? The List monad's bind strategy is to call the function on every element of the list, so the coroutine will get called repeatedly. And every time it's called, the execution pointer will move on.

I wonder whether the Python version has the same problem? I looked again at the Coro modules on CPAN, and noted that they are advertised as being able to implement "(non-clonable) continuations". I think this is the problem: I want to be able to take the point at which the next Bind will be called, and call exactly that same point multiple times (for the List monad). I asked various people including Brock again, and Scott Walters (the authors of Continuity, a continuation-based web application framework in Perl) and got the answer that Perl really doesn't do proper continuations. (As far as I understood it, they're more or less practically impossible, due to the way Perl models its execution context).

So, unless I've misunderstood (and please let me know if I have!) this technique is limited to monads that only call the bound function once (e.g. most of them except List). That's a shame though, as the List comprehension semantics would be lovely to express in a monadic do block.

Meta continuations

The Valued Lesson post does implement continuations monadically... Could we do that and then implement monadic do using these monadic continuations? I think the answer might be "Yes but my brain would explode trying to implement it".

Plan B

I think that the most sensible method may be to take the contents of the monadic do block and use the B:: modules to convert them from what looks like

my $x = bind ...;

to

... >> sub { my $x = shift;

. Which is pretty much the approach of Greg Buchholz's source filter. But I think a parse tree transformation may be more elegant. (This said, I don't know the Perl source or understand the opcodes, so it may just be slightly crazy).

Update: Some discussion on reddit, as Vox still doesn't support OpenID

"Readable Perl" talk slides up

29 May 2008 In: perl

Thanks to Chris and Thom for organising the recent GeekUp Liverpool talks.  The evening started well with Martin Owen's very interesting talk on justifying Erlang, and why FP is going to become a major factor in tomorrow's concurrency-oriented world.  I demonstrated the wisdom of always saving a copy of your talk in PDF to a FAT formatted USB drive by being able to seamlessly present from Martin's laptop when my own decided that it really wanted to do a fsck right now, thanks very much.

My talk went well, thanks in no small part to a very receptive and welcoming audience (largely non-Perl programmers).  I've now posted the slides to http://greenokapi.net/talks/ReadablePerl.pdf under a Creative Commons Attribution-Non-Commercial 2.0 UK: England & Wales license.  Comments and questions welcome!

A varied discussion ensured, spanning legacy code, the value (or not) of training, vitamins and handmade chess-sets, Liverpool city of cultcha, and much much more.

Vista hate

26 May 2008 In: windows

I'm currently suffering Vista to try to install some development software - on the one hand, for work, and on the other, to play with F# and dotNet.

I realised that I need the full version of Visual Studio (the free "Express" version doesn't allow any plugins, by design) so downloaded the 90-day trial version.  It downloads as a .iso file to burn to disk, or to mount.

Cunningly, Vista doesn't have any builtin tools to do anything with a .iso.  Now, I'm getting used to Windows not having basic functionality that you could just take for granted under Linux or Mac, but this is pretty silly for software downloaded from Microsoft itself.

A quick google for "mount iso free vista" came up with a few aborted attempts (XP only, non-free, etc.) and finally, this: Virtual CloneDrive, from SlySoft which so far seems to pleasantly Just Work.

More Liverpool talks: Perl, Erlang

16 May 2008 In: perl

Thom has posted the details of the next Liverpool geekup, Tuesday May 27th at 3345 Parr Street.
Last time, I admitted to programming in Perl and got ribbed about it being unreadable.  Fueled by the fervour of the righteous (and maybe a pint or two of Cains bitter) I volunteered to do a talk on Readable Perl.

People like to claim Perl is line noise, with its sigils and regular expressions. But a lot of the features that make it possible to write, yes, truly awful, unreadable Perl, also let you write clean, maintainable code too.

  • those $%&* sigils!
  • there's More Than One Way To Do It
  • strings and data structures
  • map, grep, first class functions
  • metaprogramming and the CPAN
  • modern Object Oriented programming with Moose

Martin Owen will also be talking on Erlang, which I'm very much looking forward to!

Hope to see you there :-)

F# talk in Liverpool

16 May 2008 In: dotnet

I'd been out of Liverpool for 3 years or so, and I completely missed GeekUp: a loosely affiliated, grassroots tech meetup society in the North West. The Liverpool branch is pretty active, and linked with various other groups, such as the DotNet user group, where Chris Alcock gave a very interesting talk on F#.

Not sure what the Dot Net programmers made of it, there were some questions afterwards amounting to "What's the point? Are academics going to use it?" :-) Which I thought was amusing as I was looking at it from the perspective of a Haskell newbie, thinking a) that's cool, b) it's simpler than Haskell, c) it plugs into the .Net libraries and development environment, and had concluded that it could (possibly) become a massive hit in real-world programming too...

Some notes, mainly comparisons with Haskell:

  • The #light pragma adds syntactic sugar, very much like the Haskell do notation. (No need for open/close/statement delimiters, whitespace significant, skip in off let statements)
  • The REPL is multiline by default (dons on #haskell noted that ghci supports this with :{
  • Functions aren't recursive by default and have to be introduced as such (let rec factorial = ...)
  • Lists aren't lazy, but there's a separate datatype called Seq which is.
  • You can use yield in a function to define lazy sequences.
  • Like Ocaml, F# supports mutable data, though the default is pure data.
  • It also supports reference types - I'm not sure I understand what these are for in a functional programming language, or if they're just for updating, then how they're different from plain old mutable data. The example Chris gave was the classic closure example of a counter function.
  • The examples he made of pipelining with |> and >> looked very much Haskellish monads (especially in a #light style block). I quickly leafed through Chris's copy of till I found the section on monads. They're called "Workflows", because that's less scary than Monads. They're also largely transparent, which on one hand is nice, as there's none of the painful and ugly "lifting" of values to the appropriate monad. (On the other, it means that you can't easily separate action and non-action code).
  • This of course means that you don't get some of the benefits of FP's purity. Martin Owen, who is keen on Erlang and its capacity for massively scalable, high performance networking, also pointed out that allowing mutability means that you can't guarantee that the application is threadsafe, and is the wrong default as we're coming up against multicore programming.

All in all, it was a very interesting talk, and I'm looking forward to playing with F#. I apt-got Mono, and promptly failed to install F# on it, as the provided install.sh script whined about something to do with gac and aot. Ah well, perhaps that's a good excuse to boot up into Windows...

Monad Wars - code online

28 Nov 2007 In: haskell

Chessguy pointed out that it's currently hard to play along with the monad wars code.

It would be nice for the posts to be “literate haskell”, where sections preceded by “>“ characters are valid Haskell. The idea is great - that you can mix sections of introduction and description with sections of actual code, ending up with an article that is also executable code! Which is very much the style of these posts, but right now I'm being too lazy to go that extra step:

  • sometimes I show multiple version of the same function, (some of them might not work)
  • I tend to introduce imports as needed but (I believe) they need to be at the top of the file
  • I don't always repeat functions from earlier posts but just refer to them

So I've posted the current source to my subversion repo. As you can see they're currently related to the number of the associated post, and contain different areas of functionality: this is actually how I'm working on it for the moment - I'm hoping to put together some of the pieces in part 5 or so (Blog Driven Development is a rather odd way to structure your work but there you go...)

Update: Vincenz on #haskell convinced me that I really should try literate haskell - watch this space...

I'm now probably going to fall off the internet for a week while I move country and job.  I'll be at the London Perl Workshop this Saturday and will talk (for a whole 5 minutes!) about Monad Wars. Maybe see you there :D

Perl snippet - repeated arrays

27 Nov 2007 In: perl

On #moose, the channel for a popular modern dialect of Object Oriented Perl, Debolaz asked:

 <Debolaz> Is there a simple way to produce an array of array 
like [[1..4],[1..4]] without having to specify the
[1..4] multiple times?

Matt Trout's reply confused me at first

 <@mst> map { [ 1..4 ] } (1, 2)

because I thought there was a more straight-forward obvious answer:

    ([1..4]) x 2

Of course, as it happens, I was wrong... there is a problem with my version, and it's clear if you print the structure out using Data::Dumper

 $VAR1 = [
[ 1, 2, 3, 4 ],
$VAR1->[0]
];

The same reference is cloned. This wouldn't be a problem in Haskell of course — being a pure language, there's no way reusing the same memory address could cause a problem. So you can just write:

  > replicate 2 [1..4]

Peregrin suggested

   (eval { [1..4] }) x2

but this also reused the array. My best solution was

use Storable qw(dclone);   
map { dclone $_ } ([1..4]) x 2

which is also quite yucky. Debolaz ended up using a variant of Matt's solution:

  map [ 1..4 ], (1,2)

I never use map EXPR, LIST style (probably I read that it could be confusing at an impressionable age...) but that does seem fairly legible.

Monad Wars - 3: Command line actions

27 Nov 2007 In: haskell

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:

  • Tokenise the line
  • Check if the first token maps to a command
  • Check if the rest of the tokens can be handled by that command.
  • The result (if applicable) is a function that maps an original GameState into a new state.

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?

Possibly Maybe

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

Let's play

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