Реализация интерфейса OO-Like в Haskell
несмотря на название, я не собираюсь спрашивать про простой перевод между OO-миром и Haskell, но я не могу понять лучшего названия. Это обсуждение похоже, но не равно, этому.
Я начал игрушечный проект только для того, чтобы расширить свои ограниченные знания о Haskell, читая "Learn You a Haskell for Great Good", и я решил реализовать очень базовую "Систему элементов элемента", которая является подмножество типичной боевой системы в таких играх, как Final Fantasy et simila.
Я пропускаю большинство деталей, но это в двух словах моя проблема:
Я хочу смоделировать заклинание, магию, которую вы можете наложить на игрока или на монстра. В мире OO вы обычно идете для интерфейса "Castable" с помощью метода "onCast (Player)", класса "Spell", чтобы вы могли определить такую вещь как
Spell myNewSpell = Spell("Fire", 100, 20);
myNewSpell.onCast(Player p); //models the behaviour for the Fire spell
В Haskell я думал об этом с точки зрения типов и классов (я знаю, что классы в Haskell - это другая концепция!). Я столкнулся с некоторыми трудностями, потому что моя первая попытка состояла в том, чтобы создать это:
--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)
Теперь предположим, что я создаю какое-то заклинание, используя синтаксис записи
bio = Spell{spellName = "Bio", ...etc..}
Я хотел бы иметь возможность сделать что-то вроде этого
instance Castable bio where
onCast bio = Left (Just Poison)
Здесь много проблем:
1) Я не могу делать "Castable bio", поскольку био должен быть конкретным типом, а не значением типа (это должно быть заклинание Castable)
2) био не находится в области видимости, внутри блока экземпляра видно, что значение соответствует шаблону
В целом, я чувствую, что этот выбор дизайна довольно низок, но я все еще участвую, и я не понимаю такие продвинутые темы, как "Фунтеры", просто чтобы назвать его.
В двух словах, что является идиоматическим способом борьбы с подобной ситуацией? Я имею в виду ситуацию, для которой требуется "одно определение, множественная реализация для нескольких экземпляров", просто для использования терминологии OO.
Спасибо всем, счастливое кодирование,
Alfredo
Ответы
Ответ 1
Типы классов полезны, когда вы имеете дело с разными типами. В этом случае, однако, мне кажется, что вы имеете дело с отдельными экземплярами. В таком случае, вероятно, проще всего, чтобы функция трансляции была просто другим полем записи.
data Spell = Spell{spellName :: String,
...
onCast :: Either (Maybe Status) CastResult }
deriving (Eq,Show,Read)
bio = Spell { spellName = "Bio", onCast = Left (Just Poison), ... }
Или вы можете сделать что-то, что более точно моделирует ваши требования, используя типы домена, а не общие, такие как 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)
Это, вероятно, сделает трюк здесь, но не уверен, полезен ли этот класс в этом случае. Это что-то другое, кроме заклинания? Что-то вроде умения или предмета?
Ответ 3
Если вы правильно поняли, я думаю, вы должны сделать onCast
дополнительное поле записи Spell
, затем вы можете написать:
bio = Spell{spellName = "Bio", ...etc.., onCast = Left (Just Poison)}
Вы больше не сможете сделать deriving (Eq,Show,Read)
, хотя, поскольку Spell
теперь содержит тип функции. Вам придется записывать эти экземпляры вручную. Изменить: на самом деле onCast не является типом функции, поэтому игнорируйте это.