Friday, January 18, 2008

Scraping my boilerplate: Generics instead of Records

I was talking last week about my trouble writing simple code to access and update record field in Haskell. While talking with justin about it, he suggested using parameter type for record object, so that the compiler would check that an update method would actually take as parameters the proper rating and the corresponding update function. That was cool enough, and that got me into the "Scrap your boilerplate" papers and Data.Generics modules. What I have now, using generics programming, is fairly simple and I think I'm getting closer to what Haskell code should be like.
First, Ratings: a Rating has three int values: the current level, normal level, and the experience points. What I do now is that each value is actually typed, and the Rating only holds a list of them:

data RatingScoreType=Normal |
Current |
Experience
deriving (Show,Read,Enum,Eq,Typeable,Data)

data RatingScore=RatingScore RatingScoreType Int
deriving (Show,Read,Typeable,Data)
data Rating=Rating [RatingScore]
deriving (Typeable,Data)

With that I can add an update method that only changes a RatingScore if the type match with the type provided:

addRS :: RatingScoreType -> Int -> RatingScore -> RatingScore
addRS t2 b rs@(RatingScore t1 a)
| t1==t2=RatingScore t1 (a+b)
| otherwise=rs


And a method that tells me if a score match the given type

getRS :: RatingScoreType -> RatingScore -> Bool
getRS t2 rs@(RatingScore t1 a)
| t1==t2=True
| otherwise=False


(note that addRS can be rewritten using getRS but we don't gain anything in code size)

I can then use Data.Generics functions to provide generic update and get functions:

addR :: Data a => RatingScoreType -> Int -> a -> a
addR rst i=everywhere (mkT (addRS rst i))

getR :: Data a => RatingScoreType -> a -> Int
getR rst a=
let (RatingScore t1 b)= head $ listify (getRS rst) a
in b


(There are probably other and maybe better way of writing these, but hey, the documentation is not very rich in examples...)

The level above Ratings are Characteristics: a character holds an array of CharacteristicRating:

data CharacteristicRating = CharacteristicRating Characteristic Rating
deriving (Show,Read,Typeable,Data)


Where Characteristic is again an Enum of all possible characteristics.

I define the similar 4 functions as above, except they filter on Characteristic first and RatingScoreType second:

addC :: Characteristic -> RatingScoreType -> Int -> CharacteristicRating -> CharacteristicRating
addC c2 rst i cr@(CharacteristicRating c1 r)
| c1==c2=everywhere (mkT (addRS rst i)) cr
| otherwise=cr

getC :: Characteristic -> RatingScoreType -> CharacteristicRating -> Bool
getC c2 rst cr@(CharacteristicRating c1 r)
| c1==c2=True
| otherwise=False

addCharacteristic :: Data a => Characteristic -> RatingScoreType -> Int -> a -> a
addCharacteristic c rst i a = everywhere (mkT (addC c rst i)) a

getCharacteristic :: Data a => Characteristic -> RatingScoreType -> a -> Int
getCharacteristic c rst a =
let cr=head $ listify (getC c rst) a
in getR rst cr


This is pretty cool: if I have a Character c:
getCharacteristic Strength Current c


Gives me the current strength! And a function can take a Data instance (a Character or anything else) and a Characteristic and do both testing and changing value in perfect safety!

Now I'm only just starting playing the Data.Generics, and more generally with Haskell program design, but this is cool. My RPG is not progressing fast, but the main game here is actually building it, not playing it!!

Friday, January 11, 2008

Nested records headaches in Haskell

I'm fighting a bit with Haskell records and named fields. This post has some valid remarks, but no real solutions. The main problem I have is managing elegantly nested records:
In my (embryonic) Haskell RPG game, a character is defined as a data type with several named fields (name, gender, traits). Traits is a collection of all characteristics (Strength, Dexterity, etc.). Each trait is itself a record with three values (normal level, current level, experience points). When an action is performed, not only we look at a rating value, we also update the rating experience as a result. So I want a function that takes a character, the name of a characteristic, and can update the proper characteristic rating.
With records
Traits { strength::Rating...}
I get a lookup function: a function strength that I can use to get the current value, so I can have a function like
testTrait :: Character -> (Traits -> Rating) -> ...
Where ... is whatever result the function returns (something like IO(Character,TestResult). But if testTrait needs to modify the record, I don't have a dynamic function to do it! And even if my code could hard code that "strength" is the trait I need, then the update mechanism is singularly cumbersome, since:
let c1=c{traits{strength{experience=1}}}
Doesn't compile (c is a character, I want to set experience of strength to 1). And if I want to modify the value (like do a +1 instead of setting to 1), then I need to unstack everything and rebuild it. Argh! Surely there is a better way?

The solution I have for the moment is that Traits contains only a list of Rating objects. Then I've defined an Enum with a constructor for each characteristic:
data Characteristic = Strength | ...
deriving (Show,Read,Eq,Enum,Bounded)
and I can have a method that combines fromEnum and !! to retrieve the right value. I can also have a little function that let me update a precise element in a list. But of course the type system does not guarantee me that every Character will have the right amount of Rating objects in the array!

It may be that my brain has been totally formatted by years of Java (in Java I could just pass the Rating object and it would get modified in place of course), but I just don't know what structure would give me total safety (enforcing that every character has the proper data) with no boilerplate code and no duplication. (I know I can define helper methods to do all of that, but I though Haskell would let me cut the amount of purely technical code needed and concentrate on the business).

Haskell syntax is not Java syntax; good or bad?

In this blog, the author expresses the opinion that an important part of a language popularity: Java was successful because it looked familiar to C++ developers, and similarly new languages should use Java syntax. This struck a chord: after all, I have started writing a pure functional language using Java syntax. I thought that having a similar syntax would help grasping the language better. But now I wonder. Not only do C++ and Java share a similar syntax, they also share some concepts (objects, etc...). If a language is intrinsically different (like a pure functional language with monads, say), then having a different syntax tells you straight away: warning! This is different, so not only you must learn a new syntax, but you must also learn a new way of programming.
So maybe having Haskell using a totally different syntax is A Good Thing. And it gives me a nice excuse to stop trying to create my own little language that looks a bit like Java but doesn't act like Java and concentrate on writing Haskell code.