我怎么写,"如果是类型类型a,那么a也是这个定义的b实例."

Ste*_*eve 49 haskell

我有一个类型类MyClass,并且它中有一个函数可以生成一个String.我想用这个暗示的实例Show,让我可以通过实施类型MyClassshow.到目前为止,我有,

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 aWrappedBar a不相交.

这个习惯用法中有很多例子在base包中运行.

在Control.Applicative有用于定义WrappedMonadWrappedArrow这个原因.

理想情况下,您可以说:

instance Monad t => Applicative t where
    pure = return
    (<*>) = ap 
Run Code Online (Sandbox Code Playgroud)

但实际上这个实例所说的是每个Applicative应该首先找到Monad的一个实例,然后派遣它.因此,虽然它打算说每个Monad都是Applicative(通过类似蕴涵的=>读取方式)它实际上说的是每个Applicative都是Monad,因为有一个实例头't'匹配任何类型.在许多方面,"实例"和"类"定义的语法是向后的.

  • 好吧,偶尔(如果很少)有用推断另一个类型类批发,但只有我这是另一个类型类的唯一目的.即你可以上课(Foo a,Bar a)=> FooBar a; 和实例(Foo a,Bar a)=> FooBar a,结果证明是完全明确的,你给了Foo和Bar组合的别名.(你需要一些扩展来使它工作)但是,是的,一般来说,由于上述问题,不应该使用类型类来完全推断其他类. (4认同)
  • 谢谢!我确信你可以从我的问题中得知,我仍然在准确地学习类型"是什么",这有很大帮助.尽管我不喜欢"设计模式",但是很高兴看到一本书详细介绍了Haskell程序中常见情况的菜谱解决方案,包括你所称的上述"习语". (3认同)

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约束.鉴于这种情况,我会做的就是用myShowMyClass代替show.您根本不需要Show约束.

  • 我会在所有函数中使用myShow而不是show,因此您根本不需要Show约束.在实例定义中,如果合适,您可以编写`myShow = show`.由于编辑中描述的问题,我会避免编写`实例MyClass a => Show a`.Dave Hinton的解决方案比UndecidableInstances更好,但我不认为你不应该为了方便而添加超类约束. (3认同)
  • 请记住,这些带有这些扩展的黑客只能在一个模块中合理安全地工作.你甚至不能使用这个模块的导出,即使你没有导出MyClass,在任何其他模块中也会有一个传递依赖于它的模块,它希望显示任何不是M​​yClass实例的模块. (2认同)

dav*_*420 5

我认为以相反的方式做到这一点会更好:

class Show a => MyClass a where
    someFunc :: a -> a

myShow :: MyClass a => a -> String
myShow = show
Run Code Online (Sandbox Code Playgroud)

  • 这里要实现的是,当你说show = myShow时,你已经失去了自定义节目的战斗,因为一个类型不能有两个不同的show函数,而实现MyClass的类型必须是Show的一个实例(称之为myShow instaid of show没什么区别.)那就是如果我想让T成为MyClass的一个实例,我必须实现myShow,最后结果是它的show函数作为Show的一个实例.相比之下,首先实现Show,然后实现MyClass,你会发现唯一的区别是函数的名称. (4认同)