【发布时间】:2015-05-24 07:48:05
【问题描述】:
假设我想为 semigroup 和 monoid 定义自己的类型类。所以我写了这段代码:
class Semigroup g where
(<>) :: g -> g -> g
class Semigroup m => Monoid m where
mempty :: m
但是还有另一种方法可以定义这些类型类之间的关系,并带有一些扩展:
class Semigroup g where
gappend :: g -> g -> g
class Monoid m where
mempty :: m
mappend :: m -> m -> m
instance Monoid m => Semigroup m where
gappend = mappend
后一种设计有一个优势——稍后我可以为 Monoid 添加更多实例。例如,如果我有一个向量空间的类型类,我以后可以将它变成一个加法组,而不必在类声明中指定它。另一方面,我不得不使用灵活的实例和不可判定的实例。
我的问题是 - 对于这种特殊情况,最好的设计是什么?
【问题讨论】:
-
better 在这里是一个非常困难的术语——我的问题是:你为什么要重新定义它?话虽如此:我更喜欢第一个(不确定的实例是 IMO 的危险信号)
-
我想编写自己的线性代数库,并希望有一些舒适的类,例如向量空间和环。 Monoid 和 Semigroup 仅用作示例。
-
是的,我猜是公平的——但这对于 SO 来说确实不是最好的问题(你可能会得到一些关闭的标志,因为从表面上看它似乎有点基于意见)——也许一个更好的主意是去做 reddit 或 haskell IRC 频道/邮件列表来讨论这个(?) - 特别是在后面,你会有很多非常聪明的人会立即告诉你为什么一个或另一个是 要走的路;)
-
实在看不出有什么优势。您能否准确说明第二个代码相对于第一个代码的收益是什么,但代价是允许无法确定的实例?
-
在我看来,灵活且不可判定的实例在实践中是无害的。但是,如果您想要一些不是幺半群的半群,第二种方法需要重叠实例,而 that 是真正痛苦的来源。重叠和不连贯的实例具有非常微妙的语义,难以掌握,很容易导致意外。