Реализация интерфейса 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 не является типом функции, поэтому игнорируйте это.