Реализация интерфейса OO-Like в Haskell - PullRequest
10 голосов
/ 15 сентября 2011

несмотря на название, я не собираюсь спрашивать о простом переводе между миром OO и Haskell, но я не могу найти лучшего названия. Это обсуждение аналогично, но не равно этому .

Я начал игрушечный проект, чтобы расширить свои ограниченные знания Хаскелла, читая «Учим Хаскелла для великого блага», и я решил внедрить очень простую «Элементарную систему типов», которая является Подмножество типичной системы сражений в играх, таких как Final Fantasy и Simila. Я пропускаю большинство деталей, но это в двух словах моя проблема:

Я хочу смоделировать заклинание, магию, которую вы можете наложить на игрока или на монстра. В мире ОО вы обычно выбираете интерфейс «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", так как bio должен быть конкретным типом, а не значением типа (это должно быть Castable Spell)

2) био не находится в области видимости, внутри блока экземпляра это просто значение для сопоставления с шаблоном

В целом, я чувствую, что этот выбор дизайна довольно скудный, но я все еще учусь и не понимаю такие сложные темы, как Functors, просто чтобы назвать одну.

В двух словах, какой идиоматический способ справиться с подобной ситуацией? Я имею в виду ситуацию, которая требует «одного определения, множественной реализации для нескольких экземпляров», просто чтобы использовать терминологию ОО.

Спасибо всем, счастливого кодирования,

Alfredo

Ответы [ 3 ]

14 голосов
/ 15 сентября 2011

Классы типов полезны, когда вы имеете дело с различными типами . Однако в этом случае мне кажется, что вы имеете дело с отдельными экземплярами . В таком случае, вероятно, проще всего использовать функцию приведения в качестве другого поля записи.

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, ... }
3 голосов
/ 15 сентября 2011

Если я вас правильно понимаю, я думаю, что вы должны сделать onCast дополнительным полем записи Spell, тогда вы можете написать:

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

Вы не сможетеделайте deriving (Eq,Show,Read) больше, так как Spell теперь содержит тип функции.Вам нужно будет написать эти экземпляры вручную. Редактировать: фактически onCast не является типом функции, поэтому игнорируйте это.

3 голосов
/ 15 сентября 2011
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)

Это, вероятно, поможет, но не уверен, что класс полезен в этом случае. Есть что-то еще, кроме заклинания? Что-то вроде навыка или предмета?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...