2012-02-05 41 views
5

我想用haskell来实现一个游戏,并且想使用一个类型类的系统来实现这个item系统。它的工作是这样的:Haskell Typeclass检查

data Wood = Wood Int 

instance Item Wood where 
    image a = "wood.png" 
    displayName a = "Wood" 

instance Flammable Wood where 
    burn (Wood health) | health' <= 0 = Ash 
        | otherwise = Wood health' 
     where health' = health - 100 

在项目和易燃类是这样的:

class Item a where 
    image :: a -> String 
    displayName :: a -> String 

class Flammable a where 
    burn :: (Item b) => a -> b 

要做到这一点,我需要一种方法来检测值是否是一个实例一个类型类。

Data.Data模块提供了类似的功能,这使我相信这是可能的。

+2

我不确定你在做什么符合Haskell类型模型。值是一个类型的实例应该是静态可证明的。 – millimoose 2012-02-05 16:13:36

+4

值不能是类型类的实例。类型是类型类的实例。 – 2012-02-05 16:23:37

+4

请参见[本FAQ文章](http://www.haskell.org/haskellwiki/FAQ#I.27m_making_an_RPG._Should_I_define_a_type_for_each_kind_of_monster.2C_and_a_type_class_for_them.3F)。 – ehird 2012-02-05 16:35:47

回答

8

类型类可能是错误的方式去这里。考虑使用普通的代数数据类型来代替,例如:

data Item = Wood Int | Ash 

image Wood = "wood.png" 
image Ash = ... 

displayName Wood = "Wood" 
displayName Ash = "Ash" 

burn :: Item -> Maybe Item 
burn (Wood health) | health' <= 0 = Just Ash 
        | otherwise = Just (Wood health') 
    where health' = health - 100 
burn _ = Nothing -- Not flammable 

如果这样做太难添加新的项目,你可以代替编码在数据类型本身的操作。

data Item = Item { image :: String, displayName :: String, burn :: Maybe Item } 

ash :: Item 
ash = Item { image = "...", displayName = "Ash", burn :: Nothing } 

wood :: Int -> Item 
wood health = Item { image = "wood.png", displayName = "Wood", burn = Just burned } 
    where burned | health' <= 0 = ash 
       | otherwise = wood health' 
      health' = health - 100 

但是,这会增加新功能的难度。同时进行这两个操作的问题被称为expression problem。有一个nice lecture on Channel 9 by Dr. Ralf Lämmel他更深入地解释这个问题,并讨论各种非解决方案,如果你有时间,值得看看。

有些方法可以解决这个问题,但它们比我说过的两种设计复杂得多,所以我建议使用其中一种,如果它适合您的需求,并且不必担心表达问题,除非必须。

+0

第一个可能工作(虽然我不认为它会没有Data.Data黑客攻击),但它会使添加更多项目(并且有超过200个)变得更加困难。 第二个根本不会工作,因为有些物品不易燃。我的意思是说你可能有易燃的“伍德”牌子,可以放入库存,并且可以打架的“僵尸”。 这两个项目有一个共同的特点,但每个都有另一个特性,而另一个没有。 – adrusi 2012-02-06 01:27:25

+0

这就是为什么我想要使用类型类,这是我必须能够测试的障碍,例如,玩家与交互的物品是否易燃,这意味着测试是否是类型类的成员。 – adrusi 2012-02-06 01:28:26

+0

@adrusi:某个类是否是某个类的成员是一个编译时属性,因此它不适合你正在尝试做的事情。从你所描述的内容来看,这听起来像是第二种方法应该起作用,尽管你可能需要一些修改。这取决于某些项目之间的共同属性。例如,如果任何物品可以具有任何属性组合,则可以为每个属性设置一个'Maybe'字段,就像我为易燃物品设置的那个字段。没有那个属性的项目在那里只有一个'Nothing'。 – hammar 2012-02-06 01:54:48

5

这里的问题:

burn :: (Item b) => a -> b 

这意味着是,burn结果值必须是多态性。它必须能够填写的任何洞的的任何Item的实例。现在

,它就是你想要写这样的事情(在假想的面向对象的语言与接口和子类)相当明显:

Interface Item { 
    String getImage(); 
    String getDisplayName(); 
} 

Interface Flammable { 
    Item burn(); 
} 

在这种代码,你说burn会产生一些项目,没有任何保证约什么样的项目它是。这是“所有人”和“存在”之间的区别。你想在Haskell代码中表达的是“存在”,但你实际上表达的是“为所有人”。现在,如果你真的肯定你想要做

“存在”的功能,你可以看看使用Existential Types。但要小心。如果你打算编写这样的代码:

if (foo instanceof Flammable) { 
    ... 
} 

然后你几乎肯定是做错了,会遇到很多痛苦和痛苦。相反,考虑哈马尔建议的替代方案。