问题
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 wich requires "one definition, multiple implementation for multiple instances", just to use the OO terminology.
Thanks everyone, happy coding,
Alfredo
回答1:
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, ... }
回答2:
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?
回答3:
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.
来源:https://stackoverflow.com/questions/7430520/oo-like-interface-implementation-in-haskell