我想使用haskell来实现游戏,并且希望使用类型系统来实现项目系统。它可以这样工作:
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
其中Item和Flammable类是这样的:
class Item a where
image :: a -> String
displayName :: a -> String
class Flammable a where
burn :: (Item b) => a -> b
为此,我需要一种方法来检测值是否是类型类的实例。
Data.Data模块提供了类似的功能,这使我相信这是可能的。
答案 0 :(得分: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,他更深入地解释了这个问题并讨论了各种非解决方案,如果你有时间,值得观看。
有解决问题的方法,但它们比我说明的两个设计复杂得多,所以如果符合你的需要,我建议使用其中一个,除非必须,否则不要担心表达问题。
答案 1 :(得分:5)
问题在于:
burn :: (Item b) => a -> b
这意味着burn
的结果值必须是多态。它必须能够为任何 Item
实例填充任何孔。
现在,很明显你正在尝试编写类似这样的东西(用虚构的OO语言编写接口和子类):
Interface Item {
String getImage();
String getDisplayName();
}
Interface Flammable {
Item burn();
}
在这种代码中,您说burn
将生成某些项,而不保证项目的类型。这是“为所有人”和“存在”之间的区别。您想在Haskell代码中表达的是“存在”,但您实际表达的是“为所有人”。
现在,如果您确实确定要执行“存在”功能,则可以查看使用Existential Types。但要注意。如果你打算编写这样的代码:
if (foo instanceof Flammable) {
...
}
然后你几乎肯定做错了,会遇到很多痛苦和痛苦。而是考虑一下hammar建议的替代方案。