我有一个类型类MyClass,并且它中有一个函数可以生成一个String.我想用这个暗示的实例Show,让我可以通过实施类型MyClass来show.到目前为止,我有,
class MyClass a where
someFunc :: a -> a
myShow :: a -> String
instance MyClass a => Show a where
show a = myShow a
Run Code Online (Sandbox Code Playgroud)
这给出了Constraint is no smaller than the instance head. 我也尝试过的错误,
class MyClass a where
someFunc :: a -> a
myShow :: a -> String
instance Show (MyClass a) where
show a = myShow a
Run Code Online (Sandbox Code Playgroud)
它给出了错误,ClassMyClass'用作类型`.
我怎样才能在Haskell中正确表达这种关系?谢谢.
我应该补充一点,我希望MyClass根据其类型发出特定字符串的特定实例来跟进这一点.例如,
data Foo = Foo
data Bar = Bar
instance MyClass Foo where
myShow a = "foo"
instance MyClass Bar where
myShow a = "bar"
main = do
print Foo
print Bar
Run Code Online (Sandbox Code Playgroud)
Edw*_*ETT 58
我希望大力反对迄今为止提出的破裂解决方案.
instance MyClass a => Show a where
show a = myShow a
Run Code Online (Sandbox Code Playgroud)
由于实例解析的工作方式,这是一个非常危险的实例!
实例解析通过在每个实例的右侧有效地进行模式匹配来进行=>,完全不考虑左侧的内容=>.
当这些实例都不重叠时,这是一件美好的事情.但是,你在这里说的是"这是一个你应该用于每个 Show实例的规则.当被问到任何类型的show实例时,你需要一个MyClass实例,所以去吧,这里是实现". - 一旦编译器决定使用你的实例,(只是因为'a'与所有东西统一),它就没有机会退回并使用任何其他实例!
如果你打开{-# LANGUAGE OverlappingInstances, IncoherentInstances #-}等等使它编译,当你去编写导入提供这个定义的模块并需要使用任何其他Show实例的模块时,你将得到不那么微妙的失败.最终你将能够使用足够的扩展来编译这些代码,但遗憾的是它不会做你认为它应该做的事情!
如果你考虑它给出:
instance MyClass a => Show a where
show = myShow
instance HisClass a => Show a where
show = hisShow
Run Code Online (Sandbox Code Playgroud)
编译器应该选哪个?
您的模块可能只定义其中一个,但最终用户代码将导入一堆模块,而不仅仅是您的模块.另外,如果另一个模块定义
instance Show HisDataTypeThatHasNeverHeardOfMyClass
Run Code Online (Sandbox Code Playgroud)
编译器完全有权忽略他的实例并尝试使用你的实例.
遗憾的是,正确的答案是做两件事.
对于MyClass的每个单独实例,您可以使用非常机械的定义定义Show的相应实例
instance MyClass Foo where ...
instance Show Foo where
show = myShow
Run Code Online (Sandbox Code Playgroud)
这是相当不幸的,但是当只考虑MyClass的几个实例时效果很好.
当你有大量的实例时,避免代码重复的方法(当类比show更复杂的时候)就是定义.
newtype WrappedMyClass a = WrapMyClass { unwrapMyClass :: a }
instance MyClass a => Show (WrappedMyClass a) where
show (WrapMyClass a) = myShow a
Run Code Online (Sandbox Code Playgroud)
这提供了新类型作为例如调度的工具.然后
instance Foo a => Show (WrappedFoo a) where ...
instance Bar a => Show (WrappedBar a) where ...
Run Code Online (Sandbox Code Playgroud)
是明确的,因为类型'模式'为WrappedFoo a和WrappedBar a不相交.
这个习惯用法中有很多例子在base包中运行.
在Control.Applicative有用于定义WrappedMonad和WrappedArrow这个原因.
理想情况下,您可以说:
instance Monad t => Applicative t where
pure = return
(<*>) = ap
Run Code Online (Sandbox Code Playgroud)
但实际上这个实例所说的是每个Applicative应该首先找到Monad的一个实例,然后派遣它.因此,虽然它打算说每个Monad都是Applicative(通过类似蕴涵的=>读取方式)它实际上说的是每个Applicative都是Monad,因为有一个实例头't'匹配任何类型.在许多方面,"实例"和"类"定义的语法是向后的.
Joh*_*n L 29
(编辑:离开身体为后代,但跳到最后为真正的解决方案)
在声明中instance MyClass a => Show a,让我们检查错误"约束不小于实例头".在这种情况下,约束是'=>'左侧的类型类约束MyClass a."实例头"是您编写实例的类之后的所有内容,在本例中a(在右侧Show).GHC中的类型推断规则之一要求约束具有比头部更少的构造函数和变量.这是所谓的" 帕特森条件 "的一部分.这些作为类型检查终止的保证.
在这种情况下,约束与头部完全相同,即a,因此它未通过此测试.您可以通过启用UndecidableInstances来删除Paterson条件检查,最有可能使用{-# LANGUAGE UndecidableInstances #-}pragma.
在这种情况下,您实际上是使用您的类MyClass作为类的类型类同义词Show.像这样创建类同义词是UndecidableInstances扩展的规范用法之一,因此您可以在此处安全地使用它.
"不可判定"意味着GHC无法证明类型检查会终止.尽管听起来很危险,但启用UndecidableInstances可能发生的最坏情况是编译器将循环,最终在耗尽堆栈后终止.如果它编译,那么显然typechecking终止,所以没有问题.危险的扩展是IncoherentInstances,它听起来很糟糕.
编辑:这种方法产生的另一个问题来自于这种情况:
instance MyClass a => Show a where
data MyFoo = MyFoo ... deriving (Show)
instance MyClass MyFoo where
Run Code Online (Sandbox Code Playgroud)
现在有两个Show for实例MyFoo,一个来自derinding子句,另一个来自MyClass实例.编译器无法决定使用哪个,因此它将使用错误消息进行挽救.如果您尝试创建MyClass类型的实例,而您无法控制已有Show实例,则必须使用newtypes来隐藏已存在的Show实例.即使没有MyClass实例的类型仍会发生冲突,因为定义instance MyClass => Show a因为定义实际上为所有可能的实现提供了实现a(上下文检查后来出现;它不涉及实例选择)
这就是错误信息以及UndecidableInstances如何让它消失.不幸的是,由于Edward Kmett解释的原因,在实际代码中使用会很麻烦.最初的推动力是避免Show在已经存在约束时指定MyClass约束.鉴于这种情况,我会做的就是用myShow从MyClass代替show.您根本不需要Show约束.
我认为以相反的方式做到这一点会更好:
class Show a => MyClass a where
someFunc :: a -> a
myShow :: MyClass a => a -> String
myShow = show
Run Code Online (Sandbox Code Playgroud)