Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OO-Like interface implementation in Haskell

Despite the title I'm not going to ask about a mere translation between OO world and Haskell, but I can't figure out a better title. This discussion is similar, but not equal, to this one.

I've started a toy project just to expand my limited knowledge of Haskell while reading "Learn You a Haskell for a Great Good", and I've decided to implement a very basic "Elemental Type System", which is a subset of a typical battle system in games like Final Fantasy et simila. I'm skipping most of the details, but this is in a nutshell my problem:

I want to model a spell, a magic you can cast on the player or on a monster. In the OO world you usually go for a "Castable" interface with a method "onCast(Player)", a "Spell" class so you can define thing like this

Spell myNewSpell = Spell("Fire", 100, 20);
myNewSpell.onCast(Player p); //models the behaviour for the Fire spell

In Haskell I thought this in terms of Types and Classes (I know that Classes in Haskell are a different concept!). I've encountered some difficulties, because my first attempt was to create this:

--A type synonim, a tuple (HP,MP)
type CastResult = (Integer,Integer)


--A castable spell can either deal damage (or restore) or
--inflict a status
class Castable s where
  onCast :: s -> Either (Maybe Status) CastResult


data Spell = Spell{spellName :: String,
                   spellCost :: Integer,
                   spellHpDmg :: Integer,
                   spellMpDmg :: Integer,
                   spellElem :: Maybe Element} deriving (Eq,Show,Read)

Now suppose I create some spell using the Record Syntax

bio = Spell{spellName = "Bio", ...etc..}

I would like be able to do something like this

instance Castable bio where
  onCast bio = Left (Just Poison)

There are many problems here:

  1. I can't do "Castable bio" since bio must be a concrete type, not a value of the Type (It should be Castable Spell)

  2. bio isn't in scope, inside the instance block is seen just as a value to pattern match against

Overall, I feel this choice of design is pretty poor, but I'm still learning and I don't grasp such advanced topics like Functors, just to name one.

In a nutshell, which is the idiomatic way to dealing with situation like this? I mean situation which requires "one definition, multiple implementation for multiple instances", just to use the OO terminology.

like image 300
Alfredo Di Napoli Avatar asked Sep 15 '11 12:09

Alfredo Di Napoli


3 Answers

Type classes are useful when you're dealing with different types. In this case, however, it seems to me like you're dealing with separate instances. In such a case, it's probably simplest to have the cast function be just another record field.

data Spell = Spell{spellName :: String,
                   ...
                   onCast :: Either (Maybe Status) CastResult }
    deriving (Eq,Show,Read)

bio = Spell { spellName = "Bio", onCast = Left (Just Poison), ... } 

Or you could do something that models your requirements more explicitly, using domain-specific types rather than generic ones like Either.

type ManaPoints = Integer
type HitPoints  = Integer

data Spell = Spell { spellName :: String,
                     spellCost :: ManaPoints,
                     spellElem :: Maybe Element,
                     spellEffect :: Effect }

data Effect = Damage  HitPoints ManaPoints
            | Inflict Status

cast :: Spell -> Player -> Player
cast spell player =
    case spellEffect spell of
        Damage hp mana = ...
        Inflict status = ...

bio  = Spell { spellName = "Bio", spellEffect = Inflict Poison, ... }
fire = Spell { spellName = "Fire", spellEffect = Damage 100 0, ... }
like image 124
hammar Avatar answered Nov 29 '22 09:11

hammar


data Spell = Spell{ spellName :: String
                  , spellCost :: Integer
                  , spellHpDmg :: Integer
                  , spellMpDmg :: Integer
                  , spellElem :: Maybe Element
                  , spellStatus :: Maybe Status
                  }
                  deriving (Eq,Show,Read)

class Castable s where
    onCast :: s -> (CastResult, Maybe Status)

instance Castable Spell where
    onCast s = ((spellHpDmg s, spellMgDmg s), spellStatus s)

This would probably do the trick here, not sure whether the class is useful in this case though. Is something else than a Spell castable? Something like a Skill, or an Item?

like image 39
Ptival Avatar answered Nov 29 '22 09:11

Ptival


If I understand you correctly, I think you should make onCast an additional record field of Spell, then you can write:

bio = Spell{spellName = "Bio", ...etc.., onCast = Left (Just Poison)}

You won't be able to do deriving (Eq,Show,Read) anymore though, as Spell now contains a function type. You'll have to write those instances manually. Edit: actually onCast isn't a function type, so ignore this.

like image 36
Sjoerd Visscher Avatar answered Nov 29 '22 09:11

Sjoerd Visscher