在测试中,我被要求推断出类型:
let pr = map head.group.sortBy(flip compare)
我自己推断出类型是:
后得出结论Ord a => [a] -> [a]
然而,当在GHCi中执行:t
时,它表示类型为:
pr :: [()] -> [()]
发生了什么事?
如果我在GHCi中这样做:
map head.group.sortBy(flip compare) [1,2,3,4,100,50,30,25,51,70,61]
我收到错误:
Couldn't match expected type `a0 -> [b0]' with actual type `[a1]'
In the return type of a call of `sortBy'
Probable cause: `sortBy' is applied to too many arguments
In the second argument of `(.)', namely
`sortBy (flip compare) [1, 2, 3, 4, ....]'
In the second argument of `(.)', namely
`group . sortBy (flip compare) [1, 2, 3, 4, ....]'
但是,如果我这样做:
sortBy(flip compare) [1,2,3,4,100,50,30,25,51,70,61]
[100,70,61,51,50,30,25,4,3,2,1]
它运作得很好。当第二个表达式使用完全相同的参数评估sortBy
时,为什么第一个表达式失败?
答案 0 :(得分:5)
你的第一个问题是Monomorphism Restriction,GHCi无法立刻看到你的整个节目的可怕组合,以及GHCi扩展的违约规则。
简而言之,Haskell并不想推断具有多态类型类约束的类型(类型签名的Ord a =>
部分)用于顶级绑定,这些绑定被写为语法的方程式没有参数。 pr = map head.group.sortBy(flip compare)
违反了这条规则(它是一个功能,所以语义上它有参数,但是你用它来定义它的等式并不是),所以Haskell希望Ord约束的a
成为具体的东西。
如果你把它放在一个源文件中并编译它(即使是通过GHCi):
import Data.List
pr = map head.group.sortBy(flip compare)
你会得到彻头彻尾的错误,例如:
foo.hs:3:33:
No instance for (Ord b0) arising from a use of `compare'
The type variable `b0' is ambiguous
Possible cause: the monomorphism restriction applied to the following:
pr :: [b0] -> [b0] (bound at foo.hs:3:1)
Probable fix: give these definition(s) an explicit type signature
or use -XNoMonomorphismRestriction
Note: there are several potential instances:
instance Integral a => Ord (GHC.Real.Ratio a)
-- Defined in `GHC.Real'
instance Ord () -- Defined in `GHC.Classes'
instance (Ord a, Ord b) => Ord (a, b) -- Defined in `GHC.Classes'
...plus 22 others
In the first argument of `flip', namely `compare'
In the first argument of `sortBy', namely `(flip compare)'
In the second argument of `(.)', namely `sortBy (flip compare)'
Failed, modules loaded: none.
对于某些类型(特别是数字类型),这种"模糊类型变量"错误出现了很多并且会令人恼火,所以Haskell有一些默认规则。例如,它假定仅由Num
约束的模糊类型变量应为Integer
。当然,如果你在同一个文件中的任何地方使用函数,那么:
import Data.List
pr = map head.group.sortBy(flip compare)
answer = pr [1,2,3,4,100,50,30,25,51,70,61]
然后Haskell可以考虑到。它仍然拒绝推断pr
的多态类型,但在这种情况下,pr
仅被用作[Integer] -> [Integer]
,因此它会给它类型并允许你的代码要编译,而不是发出模糊的类型变量错误(Integer
本身也是类型默认的结果)。
在GHCi中,您的代码一次编译一个语句,因此它无法考虑pr
的使用来决定提供它的类型。它会给你一个模糊的类型错误,除了GHCi有extended defaulting rules,它在这里开始#34;保存一天"并允许您的表达式编译。通过将Ord a => a
类型变量默认为单元类型()
,您的声明可以解释为用于将()
的任意列表压缩为[()]
(或{ {1}}如果输入为空)。谢谢GHCi!
您可以通过几种不同的方式解决此问题。一种是在[]
的定义的两边添加一个参数,如下所示:
pr
现在定义let pr z = map head.group.sortBy(flip compare) $ z
的等式有一个参数语法(它的类型/含义仍然具有相同数量的参数),单态约束不会踢在,和Haskell很高兴推断出pr
的多态类型。
另一种方法是通过在模块顶部添加pr
或在GHCi提示符下使用{-# LANGUAGE NoMonomorphismRestriction #-}
来明确告诉您不要使用Monomorphism Restriction。然后它会再次推断:set -XNomonomorphismRestriction
的类型Ord a => [a] -> [a]
。
第三种方法是明确给出函数的多态类型签名:
pr
或在GHCi中:
import Data.List
pr :: Ord a => [a] -> [a]
pr = map head.group.sortBy(flip compare)
因为即使有单态限制有效,Haskell也很高兴> let { pr :: Ord a => [a] -> [a] ; pr = map head.group.sortBy(flip compare) }
拥有多态类型,但它只是不会推断它。
显式类型签名可能是人们在编译文件中避免此问题的最常见方式,因为许多人认为始终为顶级定义提供类型签名是一种好方式。在GHCi,它很烦人,你可以看到;我通常会关闭那里的单态限制。
关于你的第二个问题,我担心这个:
pr
与此截然不同:
map head.group.sortBy(flip compare) [1,2,3,4,100,50,30,25,51,70,61]
当您将pr [1,2,3,4,100,50,30,25,51,70,61]
定义为函数时,pr
引用整个函数pr
,因此为其提供参数会为该函数提供参数。但是当你写出整个表达式时,只需将列表放在右侧,就不会将它作为参数传递给整个表达式。它解析得更像这样:
map head.group.sortBy(flip compare)
如您所见,列表是里面管道中的最后一个函数; (map head) . (group) . (sortBy (flip compare) [1,2,3,4,100,50,30,25,51,70,61])
被用作函数,它将接受一个参数并通过管道进一步提供其输出(到sortBy (flip compare) [1,2,3,4,100,50,30,25,51,70,61]
)。这显然没有意义,这就是为什么你会收到一条错误信息,抱怨有过多的论据被赋予group
;并不是说你 为sortBy
提供了太多的参数,而是你已经提供了所有的参数,然后将它用于它所具有的位置能够再拿一个。
在您习惯它之前,这有时会令人惊讶,但任何替代方案都会更频繁地令人惊讶(您隐含地依赖于在使用sortBy
和map head
时以这种方式解析)。您需要做的就是记住普通的函数应用程序(通过将两个表达式彼此相邻)总是优于中缀运算符(如sortBy (flip compare)
);每当你有一个混合中缀运算符和普通应用程序的表达式时,每个正常的应用程序链(仅由空格分隔的非运算符表达式组)就中缀运算符而言只变成一个参数(然后优先级/关联性用于解析中缀运算符的参数。)
要修复它,您需要在引入参数之前在合成管道周围添加括号,如下所示:
.
或者使用(map head.group.sortBy(flip compare)) [1,2,3,4,100,50,30,25,51,70,61]
来放置"墙"组合管道和参数之间,如下所示:
$
这是有效的,因为map head.group.sortBy(flip compare) $ [1,2,3,4,100,50,30,25,51,70,61]
是另一个中缀运算符,所以它强制所有正常的应用程序"在将一个序列应用于另一个之前,将其左侧和右侧的序列解析。它也是一个非常低优先级的运算符,因此当其他中缀运算符也在运行时(例如$
),它几乎总是有效。在Haskell中编写.
形式的表达式是一个很常见的习惯用法。
答案 1 :(得分:4)
你被defaulting所咬,其中GHCi(交互式GHCi,而不是GHC编译的东西)会在某些情况下将()
放入任何未实例化的类型参数中。
我认为你混淆了.
和$
。考虑一下你的原始表达:
map head . group . sortBy(flip compare) [1,2,3,4,100,50,30,25,51,70,61]
它组成了函数map head
,group
和sortBy (flip compare) [...]
。不幸的是,sortBy (flip compare) [...]
是一个列表,而不是一个函数,所以它不能像那样组成。但是,sortBy (flip compare)
是,如果我们将这些函数组合在一起然后将该函数应用到列表中,那么它将起作用:
map head . group . sortBy (flip compare) $ [1,2,3,4,100,50,30,25,51,70,61]