令我感到困惑的是,haskell编译器有时会推断出比我预期的更不易变形的类型,例如在使用无点定义时 .
似乎问题是“单态限制”,默认情况下在旧版本的编译器上启用 .
考虑以下haskell程序:
{-# LANGUAGE MonomorphismRestriction #-}
import Data.List(sortBy)
plus = (+)
plus' x = (+ x)
sort = sortBy compare
main = do
print $ plus' 1.0 2.0
print $ plus 1.0 2.0
print $ sort [3, 1, 2]
如果我用 ghc
编译它,我没有获得错误,可执行文件的输出是:
3.0
3.0
[1,2,3]
如果我将 main
主体更改为:
main = do
print $ plus' 1.0 2.0
print $ plus (1 :: Int) 2
print $ sort [3, 1, 2]
我没有编译时错误,输出变为:
3.0
3
[1,2,3]
正如所料 . 但是,如果我尝试将其更改为:
main = do
print $ plus' 1.0 2.0
print $ plus (1 :: Int) 2
print $ plus 1.0 2.0
print $ sort [3, 1, 2]
我收到类型错误:
test.hs:13:16:
No instance for (Fractional Int) arising from the literal ‘1.0’
In the first argument of ‘plus’, namely ‘1.0’
In the second argument of ‘($)’, namely ‘plus 1.0 2.0’
In a stmt of a 'do' block: print $ plus 1.0 2.0
尝试使用不同类型调用 sort
时会发生同样的情况:
main = do
print $ plus' 1.0 2.0
print $ plus 1.0 2.0
print $ sort [3, 1, 2]
print $ sort "cba"
产生以下错误:
test.hs:14:17:
No instance for (Num Char) arising from the literal ‘3’
In the expression: 3
In the first argument of ‘sort’, namely ‘[3, 1, 2]’
In the second argument of ‘($)’, namely ‘sort [3, 1, 2]’
-
为什么
ghc
突然认为plus
不是多态的并需要Int
参数?对Int
的唯一引用是在plus
的应用程序中,当定义明显是多态的时,这怎么可能? -
为什么
ghc
突然认为sort
需要Num Char
实例?
此外,如果我尝试将函数定义放入自己的模块中,如下所示:
{-# LANGUAGE MonomorphismRestriction #-}
module TestMono where
import Data.List(sortBy)
plus = (+)
plus' x = (+ x)
sort = sortBy compare
编译时出现以下错误:
TestMono.hs:10:15:
No instance for (Ord a0) arising from a use of ‘compare’
The type variable ‘a0’ is ambiguous
Relevant bindings include
sort :: [a0] -> [a0] (bound at TestMono.hs:10:1)
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 23 others
In the first argument of ‘sortBy’, namely ‘compare’
In the expression: sortBy compare
In an equation for ‘sort’: sort = sortBy compare
-
为什么不
ghc
能够使用Ord a => [a] -> [a]
的多态类型sort
? -
为什么
ghc
区别对待plus
和plus'
?plus
应该有多态类型Num a => a -> a -> a
,我真的不知道这与sort
的类型有什么不同,但只有sort
会引发错误 .
最后一件事:如果我评论 sort
的定义文件编译 . 但是,如果我尝试将其加载到 ghci
并检查我得到的类型:
*TestMono> :t plus
plus :: Integer -> Integer -> Integer
*TestMono> :t plus'
plus' :: Num a => a -> a -> a
为什么不是 plus
多态的类型?
这是关于Haskell中单形态限制的规范问题,如元问题中所讨论的 .
1 回答
什么是单态限制?
Haskell wiki所述的monomorphism restriction是:
这意味着,在某些情况下,如果您的类型不明确(即多态),编译器将选择将该类型实例化为不模糊的类型 .
如何解决?
首先,您始终可以显式提供类型签名,这将避免触发限制:
或者,如果要定义函数,则可以避免使用point-free style,例如:
关闭它
可以简单地关闭限制,这样您就不必对代码执行任何操作来修复它 . 该行为由两个扩展控制:
MonomorphismRestriction
将启用它(这是默认值),而NoMonomorphismRestriction
将禁用它 .您可以将以下行放在文件的最顶部:
如果您使用的是GHCi,则可以使用
:set
命令启用扩展:您还可以告诉
ghc
从命令行启用扩展:Note: 您应该更喜欢第一个选项,而不是通过命令行选项选择扩展 .
有关此扩展名和其他扩展名的说明,请参阅GHC's page .
完整的解释
我将尝试总结下面你需要知道的一切,以了解单态限制是什么,为什么它被引入以及它是如何表现的 .
一个例子
采取以下微不足道的定义:
你认为能够用
plus
替换+
的每一次出现 . 特别是自(+) :: Num a => a -> a -> a
以来你也期望plus :: Num a => a -> a -> a
.不幸的是,这种情况并非如此 . 例如,我们在GHCi中尝试以下内容:
我们得到以下输出:
您可能需要:在较新的GHCi版本中设置-XMonomorphismRestriction .
事实上,我们可以看到
plus
的类型不是我们所期望的:发生的事情是编译器看到
plus
的类型Num a => a -> a -> a
,一个多态类型 . 此外,上述定义属于我稍后将解释的规则,因此他决定通过默认类型变量a
来使类型变为单态 . 我们可以看到默认值为Integer
.请注意,如果您尝试使用
ghc
编译上述代码,则不会出现任何错误 . 这是由于ghci
如何处理(并且必须处理)交互式定义 . 基本上,在考虑以下内容之前,必须对ghci
中输入的每个语句进行完全类型检查;换句话说,就好像每个语句都在一个单独的模块中一样 . 后来我会解释为什么这件事 .其他一些例子
考虑以下定义:
我们希望所有这些功能都以相同的方式运行相同的类型,即
show
的类型:Show a => a -> String
.然而,在编译上述定义时,我们会得到以下错误:
所以
f2
和f4
不编译 . 此外,当尝试在GHCi中定义这些函数时,我们没有错误,但f2
和f4
的类型是() -> String
!单态限制是使
f2
和f4
需要单态类型的原因,而ghc
和ghci
的不同行为是由于不同的默认规则造成的 .什么时候发生?
在Haskell中,由report定义,有两种不同类型的bindings . 功能绑定和模式绑定 . 函数绑定只不过是函数的定义:
请注意,它们的语法是:
Modulo守卫和声明 . 但它们并不重要 .
哪里必须至少有一个参数 .
模式绑定是表单的声明:
同样,模数守卫 .
注意变量是模式,所以绑定:
是一种模式绑定 . 它将模式
plus
(变量)绑定到表达式(+)
.当模式绑定仅由变量名称组成时,它称为简单模式绑定 .
The monomorphism restriction applies to simple pattern bindings!
好吧,我们应该正式说:
报告第4.5.1节 .
然后(report的第4.5.5节):
我添加的例子 .
因此,受限制的声明组是一个组,其中有非简单的模式绑定(例如
(x:xs) = f something
或(f, g) = ((+), (-))
),或者存在一些没有类型签名的简单模式绑定(如plus = (+)
) .单态限制会影响 restricted 声明组 .
大多数情况下,您不定义相互递归函数,因此声明组只是一个绑定 .
它做什么?
单态限制由report的4.5.5节中的两个规则描述 .
第一条规则
突出显示的部分是单态限制引入的内容 . 它说如果类型是多态的(即它包含一些类型变量)并且该类型变量受约束(即它对它有一个类约束:例如类型
Num a => a -> a -> a
是多态的,因为它包含a
并且也是因为a
具有约束Num
在它上面 . )然后它不能一概而论 .简单来说,不泛化意味着
plus
函数的 uses 可能会改变其类型 .如果你有定义:
然后你会得到一个类型错误 . 因为当编译器在
x
的声明中看到plus
被Integer
调用时,它会将类型变量a
与Integer
统一起来,因此plus
的类型变为:但是,当它将键入检查
y
的定义时,它将看到plus
应用于Double
参数,并且类型不匹配 .请注意,您仍然可以使用
plus
而不会收到错误:在这种情况下,
plus
的类型首先被推断为Num a => a -> a -> a
,但是在x
的定义中使用它,其中1.0
需要Fractional
约束,将其更改为Fractional a => a -> a -> a
.理由
报告说:
对于这一点,我相信wiki的例子更清楚 . 考虑功能:
如果
len
是多态的,f
的类型将是:所以元组
(len, len)
的两个元素实际上可能是不同的值!但这意味着必须重复由genericLength
完成的计算以获得两个不同的值 .这里的基本原理是:代码包含一个函数调用,但不引入此规则可能会产生两个隐藏的函数调用,这是反直觉的 .
对于单态限制,
f
的类型变为:以这种方式,不需要多次执行计算 .
好吧,我相信这个例子是不言自明的 . 在某些情况下,不应用规则结果类型歧义 .
如果您按照上面的建议禁用扩展,则在尝试编译上述声明时会出现类型错误 . 然而,这不是一个真正的问题:你已经知道在使用
read
时你必须以某种方式告诉编译器应该尝试解析哪种类型...第二条规则
这意味着 . 如果您有通常的定义:
这将具有
Num a => a -> a -> a
类型,其中a
是由于上述规则1的单态类型变量 . 一旦推断出整个模块,编译器将根据默认规则选择一个将替换a
的类型 .最终结果是:
plus :: Integer -> Integer -> Integer
.请注意,这是在推断整个模块之后完成的 .
这意味着如果您有以下声明:
在模块内部,在类型默认之前
plus
的类型将是:Fractional a => a -> a -> a
(参见规则1了解为什么会发生这种情况) . 此时,遵循默认规则,a
将被Double
替换,因此我们将plus :: Double -> Double -> Double
和x :: Double
.违约
如前所述,在Section 4.3.4 of the Report中描述了一些默认规则,即推理器可以采用并且将用单态类型替换多态类型 . 只要类型不明确,就会发生这种情况 .
例如在表达式中:
这里的表达式是不明确的,因为
show
和read
的类型是:所以
x
的类型为Read a => a
. 但是这种约束可以通过很多类型来满足:例如Int
,Double
或()
. 哪一个选择?没有任何东西可以告诉我们 .在这种情况下,我们可以通过告诉编译器我们想要哪种类型来解决歧义,添加类型签名:
现在的问题是:由于Haskell使用
Num
类型类来处理数字,因此很多情况下数值表达式包含歧义 .考虑:
结果应该是什么?
和之前一样
1
的类型为Num a => a
,并且可以使用许多类型的数字 . 哪一个选择?几乎每次我们使用数字时都有编译器错误并不是一件好事,因此引入了默认规则 . 可以使用
default
声明来控制规则 . 通过指定default (T1, T2, T3)
,我们可以更改inferencer默认不同类型的方式 .如果出现以下情况,则不明确的类型变量
v
是默认的:v
只出现在C v
类型的约束中_1145297_是一个类(即如果它出现在:Monad (m v)
那么它是 not defaultable) .这些类中至少有一个是
Num
或Num
的子类 .所有这些类都在Prelude或标准库中定义 .
可缺省类型变量由
default
列表中的第一个类型替换,该列表是所有不明确变量类的实例 .默认
default
声明为default (Integer, Double)
.例如:
推断的类型是:
通过违约规则,成为:
请注意,这解释了为什么在问题的示例中只有
sort
定义引发错误 . 类型Ord a => [a] -> [a]
无法默认,因为Ord
不是数字类 .延长违约
请注意,GHCi附带extended defaulting rules(或herefor GHC8),可以使用
ExtendedDefaultRules
扩展名在文件中启用 .默认类型变量不仅需要出现在所有类都存在的约束中标准,并且必须至少有一个类在
Eq
,Ord
,Show
或Num
及其子类中 .此外,默认的
default
声明是default ((), Integer, Double)
.这可能会产生奇怪的结果 . 以问题为例:
在ghci中我们没有得到类型错误,但
Ord a
约束导致()
的默认值,这几乎没用 .有用的链接
关于单态限制有很多资源和讨论 .
以下是一些我觉得有用的链接,可以帮助您理解或深入了解该主题:
Haskell的维基页面:Monomorphism Restriction
report
无障碍且好看blog post
A History Of Haskell: Being Lazy With Class的第6.2和6.3节涉及单态限制和类型默认