使用类型类清理功能类型签名

时间:2011-06-12 16:09:00

标签: haskell typeclass

给出这样的函数签名(使用riak包):

put :: (Storable a, Resolvable a, ToJSON a, FromJSON a) => Connection -> a -> IO ()

通过定义类型类来清理函数签名是一个坏主意:

class (Storable a, Resolvable a, ToJSON a, FromJSON a) => Persistable a
put :: (Persistable a) => Connection -> a -> IO ()

我很高兴听到这是一个愚蠢的想法,但如果是这样,你能告诉我为什么吗?

其次,假设这不是一个坏主意,我可以通过使用UndecidableInstances来定义一个catch-all实例:

instance (Storable a, Resolvable a, ToJSON a, FromJSON a) => Persistable a

然而,最近有一些discussion on Haskell-cafe关于使用UndecidableInstances的缺点,普遍的共识似乎是要求使用它指出糟糕的设计决策。在这种情况下它是合理的用法,还是为每个实现Persistable的类型类要求显式的样板实例更好?

2 个答案:

答案 0 :(得分:1)

我不推荐它。您获得put的较短类型签名,但必须使用样板Persistable实例丢弃您的代码。使用UndecidableInstances解决这个(二阶)问题感觉就像使用蒸汽锤来破解坚果一样,因为UndecidableInstances can make the typechecker go in an infinite loop potentially。在我看来,缺点超过了专业人士。

Context aliases提供了一个更具吸引力的解决方案,但遗憾的是现有的编译器都不支持。

答案 1 :(得分:1)

首先,“更多样板”几乎不是最好的选择。例如,使用TH自动生成实例是另一种选择,有些人可能会发现它更适合。

那就是说,我认为UndecidableInstances得到了不好的说唱。真的,最糟糕的情况是什么?它使编译器进入无限循环?一点点小事。您可以编写几行纯Haskell 98,这将触发HM类型推断的最坏情况复杂性,并使GHC磨削停止,因为它消耗了数十亿字节的内存,这实际上比仅消耗CPU周期的无限循环更糟糕。 / p>

重要的是,如果您正在编写合理的代码,则不会发生的病理行为,并且如果您确实 ,通常很容易诊断。你不会在实例选择中得到微妙的错误或奇怪的行为,你肯定不会得到无效的程序来编译。这与在程序中始终处理 的价值水平的不确定的可能性没有什么不同。在某些方面,它更好,因为它只会在编译时导致错误,而不是运行时。

目前,用于实例的终止检查荒谬保守。有很多案例可证明是正确的,但如果没有UndecidableInstances则无法写入。虽然最好有一个更智能的终止检查程序,或者支持常见的明确安全案例的额外功能(参见Heatsink提到的上下文别名提案),但使用UndecidableInstances实现目标确实没有任何害处你现在想要的。我建议的最多就是将它的用法与只有别名的单个模块隔离开来,这样你就不用担心无意中在其他地方编写非终止实例了。

如果记忆能为我服务,那么大部分积分都是由Oleg在haskell-cafe上制作的,尽管我没有方便的链接。不过他确实talk about it on his website


相比之下,我认为OverlappingInstances可能应该比现在更糟糕。 :]在我看来,它在概念上比UndecidableInstances更具问题。