Haskell 类型类检查

发布于 2025-01-02 20:02:40 字数 605 浏览 1 评论 0原文

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

I would like to use haskell to implement a game, and would like to use a system of type classes to implement the item system. It would work something like this:

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

where the Item and Flammable classes are something like this:

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

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

To do this, I would need a way to detect whether a value is an instance of a type class.

The Data.Data module gives a similar functionality so that leads me to believe that this is possible.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

与酒说心事 2025-01-09 20:02:40

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

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

然而,这使得添加新功能变得更加困难。同时执行这两项操作的问题称为表达式问题。有一个 Ralf Lämmel 博士在第 9 频道的精彩讲座,他更深入地解释了这个问题并讨论了各种非解决方案,如果您有以下问题,非常值得观看 时间。

有多种方法可以解决这个问题,但它们比我所演示的两种设计要复杂得多,因此我建议使用其中一种(如果它符合您的需求),并且除非必须,否则不要担心表达问题。

Type classes are probably the wrong way to go here. Consider using plain algebraic data types instead, for example:

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

If this makes it too hard to add new items, you can instead encode the operations in the data type itself.

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

However, this makes it harder to add new functions. The problem of doing both at the same time is known as the expression problem. There is a nice lecture on Channel 9 by Dr. Ralf Lämmel where he explains this problem more in depth and discusses various non-solutions, well worth the watch if you have time.

There are approaches to solving it, but they are considerably more complex than the two designs I've illustrated, so I recommend using one of those if it fits your needs, and not worrying about the expression problem unless you have to.

可爱咩 2025-01-09 20:02:40

问题是:

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

这意味着 burn 的结果值必须是多态。它必须能够填充 Item任何实例的任何漏洞。

现在,很明显你正在尝试编写类似这样的东西(用具有接口和子类化的想象的 OO 语言):

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

Interface Flammable {
  Item burn();
}

在这种代码中,你说 burn 将产生一些 物品,但不保证它是什么类型的物品。这就是“对于所有人”和“存在”之间的区别。你在Haskell代码中想要表达的是“存在”,但实际上表达的是“对于所有人”。

现在,如果您确实确定要执行“存在”功能,则可以查看使用存在类型。但要小心。如果您计划编写这样的代码:

if (foo instanceof Flammable) {
  ...
}

那么您几乎肯定做错了,并且会遇到很多痛苦和痛苦。相反,请考虑哈马尔建议的替代方案。

Here's the problem:

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

What this means is that the result value of burn must be polymorphic. It must be able to fill any hole for any instance of Item.

Now, it's quite apparent you're trying to write something like this (in imaginary OO language with interfaces and subclassing):

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

Interface Flammable {
  Item burn();
}

In this sort of code, you're saying that burn will produce some item, without any guarantees about what kind of item it is. This is the difference between "for all" and "there exists". What you wanted to express in the Haskell code was "there exists", but what you actually expressed was "for all".

Now if you're really sure you want to do "there exists" functionality, you can take a look at using Existential Types. But beware. If you are planning on writing code like this:

if (foo instanceof Flammable) {
  ...
}

Then you are almost certainly doing it wrong, and will run into much pain and agony. Instead consider hammar's suggested alternatives.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文