给出这样的函数签名(使用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的类型类要求显式的样板实例更好?
答案 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
更具问题。