Let’s make an Elemental Type System in Haskell – Part I


Hello everyone,

recently I falled in love (again) with Haskell, and I’ve decided to start a simple toy project just to stretch my Haskell muscles fibers.
Meanwhile, I’ve started Final Fantasy VII again, so I thought to realize a simple Elemental Type System. I dunno how much this project will be complex, but a lot of fun awaits.

The elemental cycle

The first to do is to choose an Elemental cycle i.e. how elements influence each other. I’ve choose this simple schema (don’t blame me for the source):

For now just ignore the star in the center of the picture, for us “Fire is weak against Earth, strong against Lightning”. Ok, so we need a type representing our elements, and we need a circular one, because we want be free to invoke

pred

and

succ

without having our program to crash because we reach a boundary. Unfortunately, I haven’t found a better way to implement this without having to manually enum each element:

--We want a cyrcular enumeration for our elems
--so we can't rely on the Bounded type
data Element = Fire | Earth | Wind | Water | Lightning
  deriving (Eq, Ord, Show, Read)

instance Enum Element where
    toEnum 0 = Fire
    toEnum 1 = Earth
    toEnum 2 = Wind
    toEnum 3 = Water
    toEnum 4 = Lightning
    toEnum i = toEnum $ i `mod` 5

    fromEnum Fire = 0
    fromEnum Earth = 1
    fromEnum Wind = 2
    fromEnum Water = 3
    fromEnum Lightning = 4

    enumFromTo x y = map toEnum [a .. b']
      where a = fromEnum x
            b = fromEnum y
            b' = if a <= b then b else b + 5

    enumFromThen x1 x2 = error "enumFromThen not supported for Element"
    enumFromThenTo x1 x2 y = error "enumFromThenTo not supported for Element"

We haven’t done much yet, fun starts right on! The second step is create a monster type, through the record syntax, which allow to nicely get the type properties:

data Monster = Monster {name :: String,
                        hp :: Integer,
                        mp :: Integer,
                        elemType :: Maybe Element}
                        deriving (Eq, Read)

instance Show Monster where
    show m = "Name: " ++ name m ++ "\n" ++
             "HP: " ++ show (hp m) ++ "\n" ++
             "MP: " ++ show (mp m) ++ "\n" ++
             "Element: " ++ show (elemType m)

I’ve created an instance of Show for the type Monster for pretty printing purpose. Note how elemType is of type “Maybe Element”, just because not every monster is an elemental one! Using Maybe we can make our elemental system more flexible and less crash prone. The next step is to create four simple functions to play with monsters weakness and strenghts:

weakTo :: Monster -> Maybe Element
weakTo m = case elemType m of
                Just e -> Just $ succ e
                _ -> Nothing

strongAgainst :: Monster -> Maybe Element
strongAgainst m = case elemType m of
                       Just e -> Just $ pred e
                       _ -> Nothing

isWeakTo :: Monster -> Element -> Bool
m `isWeakTo` elem = case elemType m of
                         Just e -> elem == (succ e)
                         _ -> False

isStrongAgainst :: Monster -> Element -> Bool
m `isStrongAgainst` elem = case elemType m of
                                Just e -> elem == (pred e)
                                _ -> False

As you can see, the code is pretty straightforward. We can use our functions just like a sort of “Sense” spell, searching for monster weakness (I remember you that a spell can be more or less effective depending on the type of the target). This functions works but as you can see there is too much boilerplate code, since we are changing only a function (pred and succ) between the two. Function application operator comes in rescue:

checkProperty :: Monster -> (Element -> Element) -> Maybe Element
checkProperty m f = case elemType m of
                    Just e -> Just . f $ e
                    _ -> Nothing

checkProperty is our general purpose function who checks for monster weakness and strengths, and here are the new functions versions:

weakTo' :: Monster -> Maybe Element
weakTo' m = checkProperty m succ

strongAgainst' :: Monster -> Maybe Element
strongAgainst' m = checkProperty m pred

Wow, only one line of code! The dirty job is done by “checkProperty”, and you can write similar and shorten function even for isWeakTo and isStrongAgainst

Let’s test them!

Now the funniest part, let’s have some fun with ghci:

:l Types.hs
[1 of 1] Compiling Main             ( Types.hs, interpreted )
Ok, modules loaded: Main.
ghci> let piro = Monster{name = "Piro", hp = 70, mp = 200, elemType = Just Fire}
ghci> checkProperty piro succ
Just Earth
ghci> checkProperty piro pred
Just Lightning
ghci> :r
[1 of 1] Compiling Main             ( Types.hs, interpreted )
Ok, modules loaded: Main.
ghci> let piro = Monster{name = "Piro", hp = 70, mp = 200, elemType = Just Fire}
ghci> strongAgainst' piro
Just Lightning
ghci> weakTo' piro
Just Earth
ghci> let dragon = Monster{name = "Dragon", hp = 200, mp = 100, elemType = Nothing}
ghci> dragon `isWeakTo` Fire
False
ghci> piro `isWeakTo` Earth
True
ghci>dragon
Name: Dragon
HP: 200
MP: 100
Element: Nothing
ghci>piro
Name: Piro
HP: 70
MP: 200
Element: Just Fire

Stay tuned for other improvement! Have fun with Haskell!

Alfredo

3 thoughts on “Let’s make an Elemental Type System in Haskell – Part I

    • Thanks, I’m happy that you liked this series (already took a look to part 2 and 3?)
      My Haskell knowledge improves day by day thanks to a wonderful book called “Learn You a Haskell for a Great Good”, so this code will be better and better (I hope🙂 )
      Yea, I’ve already studied functors and applicative, but I wanna refactor my code with the more powerful (and awesome) Monads!😀
      Stay tuned!
      Alfredo

  1. Pingback: Let’s make an Elemental Type System in Haskell – Part I « Another Word For It

Lascia un commento

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione / Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione / Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione / Modifica )

Google+ photo

Stai commentando usando il tuo account Google+. Chiudi sessione / Modifica )

Connessione a %s...