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

时间:2010-07-09 14:19:03

标签: haskell

我有一个类型类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

给出错误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

给出错误,Class MyClass'用作类型`。

如何在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

6 个答案:

答案 0 :(得分:58)

我希望大力反对迄今为止提出的破裂解决方案。

instance MyClass a => Show a where
    show a = myShow a

由于实例解析的工作方式,这是一个非常危险的实例来运行!

实例解析通过在每个实例=>的右侧有效地进行模式匹配来完成,完全不考虑=>左侧的内容。

当这些实例都不重叠时,这是一件很美妙的事情。但是,你在这里说的是“这是一个你应该用于 EVERY Show实例的规则。当被要求任何类型的show实例时,你需要一个MyClass实例,所以去获取那,这就是实施。“ - 一旦编译器决定使用你的实例,(只是因为'a'与所有东西统一),它就没有机会退回并使用任何其他实例!

如果打开{-# LANGUAGE OverlappingInstances, IncoherentInstances #-}等进行编译,当你去编写导入提供此定义的模块并需要使用任何其他Show的模块时,您将获得不那么微妙的失败实例。最终你将能够使用足够的扩展来编译这些代码,但遗憾的是它不会做你认为它应该做的事情!

如果你考虑一下:

instance MyClass a => Show a where
    show = myShow

instance HisClass a => Show a where
    show = hisShow

编译器应该选哪个?

您的模块可能只定义其中一个,但最终用户代码将导入一堆模块,而不仅仅是您的模块。另外,如果另一个模块定义

instance Show HisDataTypeThatHasNeverHeardOfMyClass

编译器完全有权忽略他的实例并尝试使用你的实例。

遗憾的是,正确的答案是做两件事。

对于MyClass的每个个人实例,您可以使用非常机械的定义定义相应的Show实例

instance MyClass Foo where ...

instance Show Foo where
    show = myShow

这是相当不幸的,但是当只考虑MyClass的几个实例时效果很好。

如果你有大量的实例,那么避免代码重复的方法(当类比show要复杂得多时)就是定义。

newtype WrappedMyClass a = WrapMyClass { unwrapMyClass :: a }

instance MyClass a => Show (WrappedMyClass a) where
    show (WrapMyClass a) = myShow a

这提供了newtype作为实例调度的工具。然后

instance Foo a => Show (WrappedFoo a) where ...
instance Bar a => Show (WrappedBar a) where ...

是明确的,因为WrappedFoo aWrappedBar a的类型“模式”是不相交的。

base包中有很多这种习惯用法的例子。

在Control.Applicative中,WrappedMonadWrappedArrow的定义就是出于这个原因。

理想情况下,您可以说:

instance Monad t => Applicative t where
    pure = return
    (<*>) = ap 

但实际上这个实例所说的是每个Applicative都应该通过首先找到Monad的实例,然后派遣到它来派生。因此,虽然它打算说每个Monad都是适用的(顺便说一句,类似于=>的读法)它实际上说的是每个Applicative都是Monad,因为有一个实例头't'匹配随便哪种。在许多方面,“实例”和“类”定义的语法是向后的。

答案 1 :(得分:29)

(编辑:离开身体为子孙后代,但跳到最后寻找真正的解决方案)

在声明instance MyClass a => Show a中,让我们检查错误“约束不小于实例头”。约束是'=&gt;'左侧的类型类约束,在本例中为MyClass a。 “实例头”是您编写实例的类之后的所有内容,在本例中为aShow的右侧)。 GHC中的类型推断规则之一要求约束具有比头部更少的构造函数和变量。这是所谓的“Paterson Conditions”的一部分。这些存在是为了保证类型检查终止。

在这种情况下,约束与头部完全相同,即a,因此它未通过此测试。您可以启用UndecidableInstances来删除Paterson条件检查,最有可能使用{-# LANGUAGE UndecidableInstances #-} pragma。

在这种情况下,您基本上使用类MyClass作为Show类的类型类同义词。像这样创建类同义词是UndecidableInstances扩展的规范用法之一,所以你可以在这里安全地使用它。

'Undecidable'意味着GHC无法证明类型检查将终止。虽然这听起来很危险,但启用UndecidableInstances可能发生的最坏情况是编译器将循环,最终在耗尽堆栈后终止。如果它编译,那么显然typechecking终止,所以没有问题。危险的扩展是IncoherentInstances,它听起来很糟糕。

编辑:这种方法可能导致另一个问题:

instance MyClass a => Show a where

data MyFoo = MyFoo ... deriving (Show)

instance MyClass MyFoo where

现在有两个Show for MyFoo实例,一个来自derinding子句,另一个来自MyClass实例。编译器无法决定使用哪个,因此它将使用错误消息进行挽救。如果您尝试制作MyClass类型实例,则无法控制已有Show个实例,您必须使用newtypes来隐藏已存在的Show实例。即使没有MyClass实例的类型仍会发生冲突,因为定义instance MyClass => Show a因为定义实际上为所有可能的a提供了一个实现(上下文检查后来出现;它不涉及实例选择)

这就是错误信息以及UndecidableInstances如何让它消失。不幸的是,由于Edward Kmett解释的原因,在实际代码中使用会很麻烦。最初的推动力是避免在已经存在Show约束时指定MyClass约束。鉴于此,我要做的只是使用myShow中的MyClass而不是show。您根本不需要Show约束。

答案 2 :(得分:5)

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

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

myShow :: MyClass a => a -> String
myShow = show

答案 3 :(得分:2)

你可以编译它,但不能编译Haskell 98,你必须启用一些语言扩展:

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
-- at the top of your file

灵活实例允许在实例声明中允许上下文。我真的不知道UndecidableInstances的含义,但我会尽可能地避免。

答案 4 :(得分:1)

您可以在相关的SO问题中找到一些有趣的答案:Linking/Combining Type Classes in Haskell

答案 5 :(得分:1)

正如Ed Kmett指出的那样,对你的情况来说根本不可能。但是,如果您有权访问要为其提供默认实例的类,则可以使用默认实现将样板减少到最小,并使用所需的默认签名约束输入类型:

{-# LANGUAGE DefaultSignatures #-}

class MyClass a where
    someFunc :: a -> Int

class MyShow a where
    myShow :: a -> String
    default myShow :: MyClass a => a -> String
    myShow = show . someFunc

instance MyClass Int where
    someFunc i = i

instance MyShow Int

main = putStrLn (myShow 5)

请注意,唯一真正的样板(除了整个示例之外)减少到instance MyShow Int

有关更实际的示例,请参阅aeson s ToJSON