2014-10-20 99 views
1

寻找解释为什么下列定义不满足类型推断:为什么类型约束不够?

-- | nub' - naive Data.List.nub implementation with extra constraint 
nub' :: (Eq a, Show a) => [a] -> [a] 
nub' = foldr (\e ac -> if e `elem` ac then ac else e:ac) [] 
main = do 
    print $ nub' [] 
-- Error: No instance for (Show a0) arising from a use of ‘print’ 
-- The type variable ‘a0’ is ambiguous 
-- Note: there are several potential instances: 

而增加Integral约束使得差额

nub' :: (Eq a, Show a, Integral a) => [a] -> [a] 
nub' = foldr (\e ac -> if e `elem` ac then ac else e:ac) [] 
main = do 
    print $ nub' [] 
-- outputs `[]' 
+1

你正在定义'nub'',但是调用'nub'。这是故意的吗? – 2014-10-20 17:36:41

+0

@JohnL好抓,固定。 – 2014-10-20 17:46:08

回答

5

之所以添加Integral约束使得代码编译再次是,因为IntegralNum的一个子类别,并且这反过来使得类型变量a不可违约Integer

您可以阅读基本事实在the Haskell Report, section 4.3.4,但外卖的消息是,Num(或子类的话)的约束使编译器只是足够的信息来做出明智的猜测a应该是什么。

没有违约机制(你可以为我刚刚发现输入default()试试这个!),在GHCI进入42会给出确切的相同的错误消息,如果你想使用GHCI作为一个奇特的,这不是非常有用计算器...

+0

就是这样,谢谢。 *默认声明*,每天学习新的东西。 – 2014-10-20 17:51:07

0

该问题与nub'上的类型约束不符。问题是main中的[]是一个空列表;没有任何元素,GHC知道它知道哪种类型的列表,以及它应该使用哪个实例来满足nub'(或者它的元素实际上是否满足约束条件;程序员可以以及它打算将它作为功能列表[Double -> Double],当然不在EqShow)。

nub'的定义中的空列表没有问题,因为它没有单独使用;它与其他已知类型的变量(已知是受到类约束的特定变量,无论如何)一起传递给foldr。然后,foldr的参数类型限制空列表与传递到nub'的列表的类型相同,并且由于我们知道输入列表的元素位于ShowEq(因为编译器将如果它不能证明这一点,则不允许nub'),那么这也必须是[]

主要的是,您不要使用[]将其类型绑定到任何具体类型。特别是在一个更大的程序中,你很可能会用的结果进行进一步的计算(不仅仅是打印),这往往会告诉编译器它是什么类型。但在这里,编译器没有太多要继续;只是该类型必须与print一起使用,它基本上只是告诉它它必须是Show中的某种类型。这并没有帮助,因为我们已经知道从nub'。我们需要知道哪个类型是,因为实例对于每个单独的类型可能有不同的行为(事实上,这就是整个类型的类)。

事实上,即使在这种简单的情况下也很重要![Int](或几乎任何其他类型)将显示为[],但类型[Char](回忆String只是一个别名)将显示为""。你的意思是哪个[]?编译器如何知道?

添加Integral约束“固定”的问题,因为现在编译器正在试图猜测一些类型匹配(Eq a, Show a, Integral a)与没有足够的信息。由于Haskell标准库被设计为使用类型类来支持多个数字类型的数字文字和基本算术,所以这种“不明确的类型变量”错误在处理数​​字时几乎会出现,并且非常烦人。由于标准数字类型是彼此的“近似值”,因此我们经常使用不在乎哪一个被使用(只要它支持我们的代码中使用的操作)。因此,Haskell有一个“违约”机制来任意选择一种类型,该类型满足数字类型约束(如Integral,Floating,等)。这仅适用于同一类型变量上的所有其他约束都是其他前奏类(其中EqShow,所以你很幸运)。

因此,基本上,在Integral约束nub' propogates这个约束[]main增加,并使得哈斯克尔认为现在一定是某种Integral号的列表,所以它只是默认为Integer。这实际上并没有做任何事情来解决存在不明确的类型变量的基本问题,它只是跳过一个功能,任意解决某些非常特定的含糊问题。

实际上决心在实际的程序这一问题,Haskell的程序员一般会说什么他们的意思,并添加一个类型声明为空列表:print $ nub' ([] :: [Bool])如果你认为它是布尔值的列表, 例如。

此外,问题根本不在nub',它在main。约束nub越来越多的类型类将不会使main中的实际问题消失,它只会创建更多不明确的类型类使用情况。将nub'更改为一个特定类型,就像nubInt :: [Int] -> [Int]会工作;但然后你有效地静态声明你的[]类型为[Int]你可以选择专门的nub变体,所以为什么不跳过专门的nub定义,只是直接静态声明[] :: [Int],而不是通过nub'横向更多的工作?

在实际的代码中,这只是一个不实际的问题,因为我们没有花所有的时间去尝试show空列表。如果列表无论您传递给它的任何函数的结果是以什么方式将元素类型与满足所有约束的具体类型或对当前已具有该类型的函数的输入相关联那么GHC就有足够的信息来挑选一个实例,而不需要任何额外的输入。这是一个不幸的类错误,对于初学者来说,使用小代码片段的错误比对有经验的Haskellers编写代码来实际执行某些事情的错误要多得多。恰恰是最能够理解/修复它的程序员的类型。