【问题标题】:Haskell Typeclass InspectionHaskell 类型类检查
【发布时间】:2012-02-05 16:11:08
【问题描述】:

我想用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 模块提供了类似的功能,因此我相信这是可能的。

【问题讨论】:

  • 我不确定你在做什么适合 Haskell 类型的模型。值是类型类的实例应该是静态可证明的。
  • 值不能是类型类的实例。类型是类型类的实例。

标签: haskell metaprogramming


【解决方案1】:

类型类可能是错误的方式。考虑改用普通的代数数据类型,例如:

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,他更深入地解释了这个问题并讨论了各种非解决方案,如果你有时间,非常值得一看。

有一些方法可以解决它,但它们比我说明的两种设计要复杂得多,所以如果它符合您的需要,我建议使用其中一种,除非您必须这样做,否则不要担心表达问题。

【讨论】:

  • 第一个可能有效(尽管我认为没有 Data.Data hack 不会),但它会使添加更多项目变得更加困难(其中有 200 多个) .第二个根本不起作用,因为有些物品不易燃。我的意思是你可能有Wood,它是易燃的,可以放入库存,Zombie,它是易燃的,可以战斗。这两个项目有一个共同的特点,但也有另一个特点,另一个没有。
  • 这就是我想使用类型类的原因,障碍是我必须能够测试,例如,玩家与之交互的物品是否易燃,这意味着测试是否is 是类型类的成员。
  • @adrusi:某物是否是类型类的成员是编译时属性,因此它并不真正适合您正在尝试做的事情。根据您的描述,听起来第二种方法应该可行,尽管您可能需要进行一些修改。这取决于某些项目之间的共同属性。例如,如果任何项目可以具有任何属性组合,则您可以为每个属性设置一个 Maybe 字段,就像我为易燃物品设置的那样。没有该属性的项目只会有一个Nothing
【解决方案2】:

问题来了:

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

这意味着burn的结果值必须是多态的。它必须能够为Itemany 实例填充any 孔。

现在,很明显您正在尝试编写类似这样的东西(使用具有接口和子类的虚构 OO 语言):

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

Interface Flammable {
  Item burn();
}

在这种代码中,您是说burn 将产生一些 项,但不保证它是什么样的 项。这就是“为所有人”和“存在”之间的区别。你想在 Haskell 代码中表达的是“存在”,但实际表达的是“为所有人”。

现在,如果您真的确定要执行“存在”功能,您可以使用Existential Types 看看。但要小心。如果您打算编写这样的代码:

if (foo instanceof Flammable) {
  ...
}

那么你几乎肯定做错了,并且会遇到很多痛苦和痛苦。而是考虑 hammar 建议的替代方案。

【讨论】:

    猜你喜欢
    • 2017-12-04
    • 1970-01-01
    • 2023-04-03
    • 1970-01-01
    • 1970-01-01
    • 2014-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多