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:
I can't do "Castable bio" since bio must be a concrete type, not a value of the Type (It should be Castable Spell)
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.
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, ... }
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?
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 Edit: actually onCast isn't a function type, so ignore this.deriving (Eq,Show,Read)
anymore though, as Spell
now contains a function type. You'll have to write those instances manually.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With