为什么类型约束不够?

时间:2014-10-20 17:05:23

标签: haskell

寻找解释为什么以下定义不满足类型推断:

-- | 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 `[]'

2 个答案:

答案 0 :(得分:5)

添加Integral约束的原因使代码再次编译是默认机制启动,因为IntegralNum的子类,而这又使类型变量a 默认Integer

您可以阅读the Haskell Report, section 4.3.4中的细节,但收集消息是Num(或其子类)约束为编译器提供了足够的信息以便做出明智的猜测a应该是。

没有默认机制(您可以通过输入我刚发现的default ()来尝试此操作!),在GHCi中输入42会给出完全相同的错误消息,而且不是如果你想使用GHCi作为花哨的计算器,这非常有用......

答案 1 :(得分:0)

问题不在于nub'的类型限制。问题是[]中的main是一个空列表; GHC已经知道了哪种类型的列表应该用来满足nub'的约束(或者它的元素是否实际上不满足)这些约束;程序员可能会将它作为一个函数列表[Double -> Double],当然不在EqShow中。

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

总的来说,你不能以一种将其类型与任何具体类型联系起来的方式使用[]。特别是,在较大的程序中,您很可能使用 nub' []的结果进行进一步的计算(而不仅仅是打印),这通常会告诉编译器它是什么类型的是。但是编译器在这里还有很多工作要做;只有该类型必须是与print一起使用的东西,它基本上只是告诉它它必须是Show中的某种类型。这没有任何帮助,因为我们已经从nub'知道了这一点。我们需要知道它是哪种类型,因为实例对于每个单独的类型可以有不同的行为(实际上,这是类型类的整个点)。

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

添加Integral约束"已修复"问题是因为现在编译器试图猜测某些匹配(Eq a, Show a, Integral a)的类型没有足够的信息。因为Haskell标准库被设计为使用类型类来支持数字文字和多种数字类型的基本算术,所以这种类型的模糊类型变量"在使用数字时几乎所有时间都会出现错误,并且非常烦人。由于标准数字类型是"近似值"彼此之间,我们常常不关心使用哪一个(只要它支持我们的代码中使用的操作)。所以Haskell有一个"默认"任意选择满足数字类型类约束的类型(例如IntegralFloatingNum等)的机制。这仅适用于同一类型变量的所有其他约束是其他前奏类(EqShow都是,所以你在这里运气好。)

所以基本上,将Integral约束添加到nub'会将此约束传播到[]中的main,并使Haskell认为它现在必须是一些列表类似Integral个数字,因此默认为Integer。这实际上并没有做任何事情来解决存在模糊类型变量的潜在问题,它只是绊倒了一个任意解决某些非常特殊的模糊性的特征。

实际在真实程序中解决此问题,Haskell程序员通常会说出他们的意思并在空列表中添加类型声明:{{1}例如,如果你认为它是一个布尔值列表。

同样,print $ nub' ([] :: [Bool])中的nub'中的问题根本不存在。main。将nub约束为越来越多的类型类将不会使main中的真正问题消失,它只会创建更多模糊类型类使用的情况。将nub'更改为特定类型,例如nubInt :: [Int] -> [Int]就可以了;但是,您可以通过选择专门的nub变体有效地静态声明[]类型为[Int],那么为什么不跳过专门的nub定义,只是静态声明[] :: [Int]直接而不是通过nub'横向进行更多的工作?

在实际代码中,它通常不是一个实际问题,因为我们不会花费所有时间来尝试show空列表。如果列表您传递给它的任何函数的结果以任何方式使用,它将元素类型与满足所有约束的具体类型或输入已经具有类型约束的当前函数,然后GHC有足够的信息来选择一个实例而无需您的任何其他输入。这是一个令人遗憾的错误类型,对于初学者来说,使用小代码片段比使用经验丰富的Haskeller编写代码实际执行某些操作要多得多;正是那些最不能理解/修复它的程序员的类型。