Friday, November 10, 2006

My first Haskell adventure game!

After years of coding mainly in Java, I have taken the plunge and looked
into a pure functional language. Haskell is quite popular these days, so I decided to read a book
on it and play around with it myself.
I got a bit frustrated at the maths or geometry examples, when I got to this great article Casting spels in LISP that uses the medium of a text based adventure game to teach the basic notions of LISP.
So I coded a very little (400 lines with data and documentation) text-based game in Haskell.
The code can be found here (You need a little extra module found here). In my enthusiasm I even added some Haddock documentation here.
The main features are:
- use of the IO monad to get user input and give back feedback
- use of the IO monad to save and load gaves
- use of the Data.Map structure
- use of the Show and Read classes to serialize and deserialize a Map structure
- pure functional handling of state: every change in the game state recreates an instance of the
GameState object. Yes, one day I'll look into the State monad to simplify these functions

My first impressions of Haskell:
- the syntax is quite hard to grasp, with loads of different signs. The indenting drove me mad sometimes.
- thanks for the static typing and the compilation. I found that the hardest part was to get the program to compile.
Once the program compiled all right it usually worked as intended.
- I know that pattern matching and guards are only syntaxic sugar for case statements, but I found them (the patterns, the guards) a lot more elegant and easy to understand
- I need to learn how to deal better with "newtype" and pattern matching. I used "newtype" instead of type for some data types because I needed to declare them as instances of Show and Read, but that forced me to use as-patterns for game state.
Surely there's simpler ways to do that
- yes, I know about $ to avoid parenthesis for function parameters, but with a good editor it's ok to manage

Now, if anybody has tips on how to improve the code I'll take them on board. I'm going to look at separating the data from the code, maybe with a simple DSL and parser. And yes, the state or IORef monads...

11 comments:

Paul R. Potts said...

Hi JP,

Thank you for making this available!

I am looking into Haskell for two purposes: one, as a vehicle for better learning functional programming idioms as I see them in languages like Scheme and Common Lisp (fold and map) since with lazy evaluation and no opportunity to slip in mutation, they seem to make more sense in Haskell. And two, I have hopes of using Haskell to model some of the embedded programs I write. But embedded programs tend to largely manage state. In C++ this is done with lots of imperative constructs, of course. I'm interested in seeing an example of managing state in a functional language.

I'm not in a position to offer any advice yet but I will see if I can start by understanding your program. Thanks again!

Harald Korneliussen said...

Yes, the indentation... it took me a while to figure out why the compiler in one place insisted that I put the "else" further in than the "if"!

Anonymous said...

capitalize == map toUpper

You should really avoid explicit recursion.

JP Moresmau said...

capitalize only puts the first letter of the string in upper case. Generally speaking, why is it better to avoid recursion? Code is clearer? More efficient?

Anonymous said...

The reason to avoid recursion is to avoid repeating yourself.

Instead of mixing your custom logic for your specific case in with the code for a standard recursion pattern, it's better if the standard recursion patterns are implemented in general functions - like map and fold.

Then the function specific to your problem can simply reference the recursion pattern it requires, rather than duplicating it.

I can't comment on the specific code because it doesn't appear to be there anymore, but this is the general reason to avoid using recursion inside a function with a specific single purpose.

Ed James said...
This comment has been removed by a blog administrator.
JP Moresmau said...

Links updated, they work now. Thanks for your interest!

Ed James said...

Hi mate. Thanks for the quick response.Please would you kindly remove my previous comment - I stupidly posted my email address DOH!
Thanks.

migmit said...

Haddock documentation is unreadable - it's text/plain MIME type. Please, make it text/html.

JP Moresmau said...

Yeah, it's not a fully functionnal web application I have there. Changed it a bit so that the html is a bit more readable.

Dan Burton said...

Instead of explicit recursion on the lookOne method, try this:

lookOne gs = map (lookInState gs)

Partially applying lookInState with the supplied game state makes it a function that takes an ItemCode and returns a String. So describing the lookOne method as a mapping function makes sense to me. (I tested it, it works)

Thanks for making your code freely available. Haskell is fun.