关于何时使用Haskell typeclasses的最终指南?

关于何时使用Haskell typeclasses的最终指南?,haskell,types,functional-programming,typeclass,Haskell,Types,Functional Programming,Typeclass,因此,我非常了解代数类型和类型类,但我对其软件工程/最佳实践方面感兴趣 现代的共识是什么,如果有的话?他们是邪恶的吗?它们方便吗?是否应该使用,何时使用 这是我的案例研究。我正在写一个RTS风格的游戏,我有不同类型的“单位”(坦克、侦察兵等)。假设我想获得每个单元的最大生命值。关于如何定义其类型,我的两个想法如下: ADT的不同构造函数: data Unit = Scout ... | Tank ... maxHealth :: Unit -> Int maxHealth Scout

因此,我非常了解代数类型和类型类,但我对其软件工程/最佳实践方面感兴趣

现代的共识是什么,如果有的话?他们是邪恶的吗?它们方便吗?是否应该使用,何时使用

这是我的案例研究。我正在写一个RTS风格的游戏,我有不同类型的“单位”(坦克、侦察兵等)。假设我想获得每个单元的最大生命值。关于如何定义其类型,我的两个想法如下:

ADT的不同构造函数:

data Unit = Scout ... | Tank ...

maxHealth :: Unit -> Int

maxHealth Scout  = 10

maxHealth Tank = 20
Typeclass为Unit,每种类型都是一个实例

class Unit a where
    maxHealth :: a -> Int

instance Unit Scout where
    maxHealth scout = 10

instance Unit Tank where
    maxHealth tank = 20
显然,最终产品中会有更多的字段和功能。(例如,每个单元将具有不同的位置等,因此并非所有功能都是恒定的)

诀窍是,可能有一些函数对某些单元有意义,但对其他单元没有意义。例如,每个单位都有一个getPosition功能,但坦克可能有一个getArmour功能,这对于没有装甲的侦察员来说没有意义


如果我希望其他Haskell程序员能够理解并遵循我的代码,那么哪一种是“普遍接受的”编写方法?

大多数Haskell程序员不喜欢不必要的类型类。这些伤害类型推断;如果没有技巧,你甚至不能列出
单元
s;在GHC中,所有的秘密字典都在传递;它们不知何故使黑线鳕更难阅读;他们会导致脆弱的等级制度。。。也许其他人可以给你更多的理由。我想一个很好的规则是在逃避它们更痛苦的时候使用它们。例如,如果没有
Eq
,您必须手动传递函数来比较,例如,两个
[[[Int]]]]
s(或使用一些特别的运行时测试),这是ML编程的难点之一

看一看。你的第一个使用求和类型的方法是可以的,但是如果你想让用户用新的单位或者其他什么来修改游戏,我建议你使用类似的方法

data Unit = Unit { name :: String, maxHealth :: Int }

scout, tank :: Unit
scout = Unit { name = "scout", maxHealth = 10 }
tank  = Unit { name = "tank",  maxHealth = 20 }

allUnits = [ scout
           , tank
           , Unit { name = "another unit", maxHealth = 5 }
           ]
在您的示例中,您需要对某个地方进行编码,即坦克有装甲,而侦察机没有。明显的可能性是用额外的信息来增加单位类型,比如一个
可能是装甲
字段或一个特殊能力列表。。。不一定有一个确定的方法


一个重量级的解决方案,可能有点过头了,就是使用一个像乙烯基这样的库,它提供可扩展的记录,给你一种形式的子类型。

对于使用TypeClass的确切时间,我不打算给出一个答案,但我目前正在编写一个库,它使用你为Unit类描述的两种方法。我通常倾向于使用sum类型,但是typeclass方法有一个很大的优势:它可以在
单位
s之间提供类型级别的区别

这迫使您对接口进行更多的写入,因为任何需要在
Unit
s上多态的函数必须只使用最终基于抽象类型类定义的函数。在我的例子中,它还重要地允许我使用
Unit
-type类型作为幻影类型中的类型参数

例如,我正在编写一个Haskell绑定到(ZMQ的原始作者提供的一个类似ZMQ的项目)。在Nanomsg中,您有共享表示和语义的
Socket
类型。每个
套接字
s正好有一个
协议
,并且某些函数只能在特定
协议
套接字
s上调用。我可以让这些函数抛出错误或返回
可能
s,但我将
协议
s定义为单独的类型,所有类型共享一个类

class Protocol p where ...

data Protocol1 = Protocol1
data Protocol2 = Protocol2

instance Protocol Protocol1 where ...
instance Protocol Protocol2 where ...
并且具有
Socket
s具有幻影类型参数

newtype Socket p = Socket ...
现在我可以把调用错误协议上的函数作为类型错误

funOnProto1 :: Socket Protocol1 -> ...

而如果
Socket
只是一个求和类型,那么在编译时就不可能进行检查。

我倾向于只在手动生成和传递实例变得一团糟时才使用类型类。在我编写的代码中,这几乎是不可能的

当您从中复制和粘贴链接时,您甚至都不需要复制链接。可耻的是:P@jmite:修复,抱歉。只是要指出:幻影类型/GADT不需要这种编程风格-你可以实现与我在回答中链接到的博客文章中提倡的类型类自由“直接风格”同等的安全性。这是真的。我没有很好地比较直接风格,只是副产品风格。在我自己的应用程序中,direct style感觉很烦人,所以我选择了一个类。我们会看看我是否保留它。我明白你的要求,但顺便说一句,你上面的代码是不合法的,不会编译。Scout和Tank是同一类型单元的不同构造函数-不能为每个构造函数定义单独的实例。您是否打算使用DataTypes扩展?您也可以阅读我对类似问题的回答。