Haskell中的OO-Like接口实现

时间:2011-09-15 12:07:27

标签: haskell types interface

尽管标题我不会问OO世界和Haskell之间的翻译,但我无法找到更好的标题。这个讨论与this one类似,但并不相同。

我开始了一个玩具项目,只是为了扩展我对Haskell的有限知识,同时阅读“为了一个伟大的好事学习你的哈斯克尔”,我决定实施一个非常基本的“元素类型系统”,这是一个Final Fantasy et simila等游戏中典型战斗系统的子集。 我正在跳过大部分细节,但这简直就是我的问题:

我想模拟一个咒语,一个可以施放在玩家或怪物身上的魔法。在OO世界中,你通常使用“onCast(播放器)”方法进行“可投射”界面,这是一个“拼写”类,所以你可以定义这样的东西

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必须是具体的类型,而不是Type的值(它应该是Castable Spell)

2)bio不在范围内,实例块内部被视为与

模式匹配的值

总的来说,我觉得这种设计选择很差,但我还在学习,而且我没有掌握像Functors这样的高级主题,只是为了命名。

简而言之,处理这种情况的惯用方法是什么?我的意思是需要“一个定义,多个实例的多个实现”的情况,只是为了使用OO术语。

谢谢大家,快乐编码,

阿尔弗雷

3 个答案:

答案 0 :(得分:14)

当您处理不同的类型时,类型类很有用。但是,在这种情况下,我似乎在处理单独的实例。在这种情况下,将cast函数只是另一个记录字段可能是最简单的。

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, ... }

答案 1 :(得分:3)

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)

这可能会解决这个问题,但不确定这个类在这种情况下是否有用。除了法术施法者之外还有别的东西吗?像技能或物品?

答案 2 :(得分:3)

如果我理解正确,我认为你应该onCast Spell的另一个记录字段,然后你可以写:

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

您将无法再执行deriving (Eq,Show,Read),因为Spell现在包含一个函数类型。您必须手动编写这些实例。编辑:实际上onCast不是函数类型,所以请忽略它。