什么是单态限制?
What is the monomorphism restriction?
我对 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
不能为 sort
使用多态类型 Ord a => [a] -> [a]
?
- 为什么
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 中关于单态限制的规范问题
正如 [元问题](https://meta.whosebug.com/questions/294053/can-we-provide-a-canonical-questionanswer-for-haskells-monomorphism-restrictio) 中所讨论的那样。
什么是单态限制?
Haskell wiki 所述的 monomorphism restriction 是:
a counter-intuitive rule in Haskell type inference.
If you forget to provide a type signature, sometimes this rule will fill
the free type variables with specific types using "type defaulting" rules.
这意味着,在某些情况下,如果您的类型不明确(即多态)
编译器将选择 实例化 该类型为明确的类型。
我该如何解决?
首先,您始终可以明确提供类型签名,这将
避免触发限制:
plus :: Num a => a -> a -> a
plus = (+) -- Okay!
-- Runs as:
Prelude> plus 1.0 1
2.0
请注意,只有变量上的普通类型签名才算作此目的,表达式类型签名不算数。例如,这样写仍然会导致限制被触发:
plus = (+) :: Num a => a -> a -> a
或者,如果你正在定义一个函数,你可以避免 point-free style,
例如写:
plus x y = x + y
正在关闭
可以简单地关闭限制,这样你就不必做
任何你的代码来修复它。该行为由两个扩展控制:
MonomorphismRestriction
将启用它(这是默认设置),同时
NoMonomorphismRestriction
将禁用它。
您可以将以下行放在文件的最顶部:
{-# LANGUAGE NoMonomorphismRestriction #-}
如果您使用的是 GHCi,则可以使用 :set
命令启用扩展:
Prelude> :set -XNoMonomorphismRestriction
您还可以告诉 ghc
从命令行启用扩展:
ghc ... -XNoMonomorphismRestriction
注意:您应该更喜欢第一个选项,而不是通过 command-line 选项选择扩展。
请参阅 GHC's page 了解此扩展和其他扩展的解释。
完整的解释
我将尝试在下面总结您需要了解的所有内容,以了解
单态限制是,为什么引入它以及它的行为方式。
一个例子
采用以下简单的定义:
plus = (+)
您认为能够用 plus
替换每次出现的 +
。特别是因为 (+) :: Num a => a -> a -> a
你期望也有 plus :: Num a => a -> a -> a
.
不幸的是,情况并非如此。例如,如果我们在 GHCi 中尝试以下操作:
Prelude> let plus = (+)
Prelude> plus 1.0 1
我们得到以下输出:
<interactive>:4:6:
No instance for (Fractional Integer) arising from the literal ‘1.0’
In the first argument of ‘plus’, namely ‘1.0’
In the expression: plus 1.0 1
In an equation for ‘it’: it = plus 1.0 1
您可能需要 :set -XMonomorphismRestriction
在较新的 GHCi 版本中。
事实上我们可以看到 plus
的类型不是我们所期望的:
Prelude> :t plus
plus :: Integer -> Integer -> Integer
发生的事情是编译器发现 plus
具有类型 Num a => a -> a -> a
,一个多态类型。
此外,碰巧上面的定义属于我稍后将解释的规则,所以
他决定通过 默认 类型变量 a
使类型成为单态的。
正如我们所见,默认值为 Integer
。
请注意,如果您尝试使用 ghc
编译 上述代码,您将不会收到任何错误。
这是由于 ghci
处理(并且 必须 处理)交互式定义的方式。
基本上在 ghci
中输入的每个语句都必须 完全 之前进行类型检查
考虑以下因素;换句话说,就好像每个语句都在一个单独的
模块。稍后我会解释为什么这很重要。
其他一些例子
考虑以下定义:
f1 x = show x
f2 = \x -> show x
f3 :: (Show a) => a -> String
f3 = \x -> show x
f4 = show
f5 :: (Show a) => a -> String
f5 = show
我们希望所有这些函数都以相同的方式运行并具有相同的类型,
即 show
的类型:Show a => a -> String
.
然而,在编译上述定义时,我们得到以下错误:
test.hs:3:12:
No instance for (Show a1) arising from a use of ‘show’
The type variable ‘a1’ is ambiguous
Relevant bindings include
x :: a1 (bound at blah.hs:3:7)
f2 :: a1 -> String (bound at blah.hs:3:1)
Note: there are several potential instances:
instance Show Double -- Defined in ‘GHC.Float’
instance Show Float -- Defined in ‘GHC.Float’
instance (Integral a, Show a) => Show (GHC.Real.Ratio a)
-- Defined in ‘GHC.Real’
...plus 24 others
In the expression: show x
In the expression: \ x -> show x
In an equation for ‘f2’: f2 = \ x -> show x
test.hs:8:6:
No instance for (Show a0) arising from a use of ‘show’
The type variable ‘a0’ is ambiguous
Relevant bindings include f4 :: a0 -> String (bound at blah.hs:8:1)
Note: there are several potential instances:
instance Show Double -- Defined in ‘GHC.Float’
instance Show Float -- Defined in ‘GHC.Float’
instance (Integral a, Show a) => Show (GHC.Real.Ratio a)
-- Defined in ‘GHC.Real’
...plus 24 others
In the expression: show
In an equation for ‘f4’: f4 = show
所以 f2
和 f4
不编译。此外,在尝试定义这些功能时
在 GHCi 中,我们得到 没有错误 ,但是 f2
和 f4
的类型是 () -> String
!
单态限制使得 f2
和 f4
需要单态
类型,ghc
和 ghci
之间的不同行为是由于不同的
默认规则。
什么时候发生?
在Haskell中定义为report, there are two distinct type of bindings。
函数绑定和模式绑定。函数绑定无非就是
一个函数的定义:
f x = x + 1
注意他们的语法是:
<identifier> arg1 arg2 ... argn = expr
模守卫和where
声明。但它们并不重要。
其中必须有至少一个参数。
模式绑定是以下形式的声明:
<pattern> = expr
同样,模守卫。
注意变量是模式,所以绑定:
plus = (+)
是一个模式绑定。它将模式 plus
(一个变量)绑定到表达式 (+)
.
当模式绑定只包含一个变量名时,它被称为
简单 模式绑定。
单态限制适用于简单模式绑定!
嗯,正式地我们应该这样说:
A declaration group is a minimal set of mutually dependent bindings.
的第 4.5.1 节
然后(report 的第 4.5.5 节):
a given declaration group is unrestricted if and only if:
every variable in the group is bound by a function binding (e.g. f x = x
)
or a simple pattern binding (e.g. plus = (+)
; Section 4.4.3.2 ), and
an explicit type signature is given for every variable in the group that
is bound by simple pattern binding. (e.g. plus :: Num a => a -> a -> a; plus = (+)
).
我加的例子
所以 restricted 声明组是一个组,其中要么
non-simple 模式绑定(例如 (x:xs) = f something
或 (f, g) = ((+), (-))
)或
有一些没有类型签名的简单模式绑定(如 plus = (+)
)。
单态限制影响限制声明组。
大多数时候你没有定义相互递归函数,因此没有声明
组变成 a 绑定。
它有什么作用?
单态性限制由部分中的两个规则描述
report.
的4.5.5
第一条规则
The usual Hindley-Milner restriction on polymorphism is that only type
variables that do not occur free in the environment may be generalized.
In addition, the constrained type variables of a restricted declaration
group may not be generalized in the generalization step for that group.
(Recall that a type variable is constrained if it must belong to some
type class; see Section 4.5.2 .)
突出显示的部分是单态限制引入的内容。
它说 if 类型是多态的(即它包含一些类型变量)
和 类型变量受到约束(即它具有 class 约束:
例如Num a => a -> a -> a
类型是多态的,因为它包含 a
和
也受到约束,因为 a
对其有约束 Num
。)
那么不能一概而论
简单来说不概括意味着函数plus
的使用可能会改变它的类型。
如果你有定义:
plus = (+)
x :: Integer
x = plus 1 2
y :: Double
y = plus 1.0 2
然后你会得到一个类型错误。因为当编译器看到 plus
是
在 x
的声明中调用 Integer
它将统一类型
变量 a
与 Integer
因此 plus
的类型变为:
Integer -> Integer -> Integer
但是,当它检查 y
的定义时,它会看到 plus
应用于 Double
参数,但类型不匹配。
请注意,您仍然可以使用 plus
而不会出现错误:
plus = (+)
x = plus 1.0 2
在这种情况下,plus
的类型首先被推断为 Num a => a -> a -> a
但随后它在 x
的定义中使用,其中 1.0
需要一个 Fractional
约束,将其更改为 Fractional a => a -> a -> a
.
理由
报告说:
Rule 1 is required for two reasons, both of which are fairly subtle.
Rule 1 prevents computations from being unexpectedly repeated.
For example, genericLength
is a standard function (in library Data.List
)
whose type is given by
genericLength :: Num a => [b] -> a
Now consider the following expression:
let len = genericLength xs
in (len, len)
It looks as if len
should be computed only once, but without Rule 1 it
might be computed twice, once at each of two different overloadings.
If the programmer does actually wish the computation to be repeated,
an explicit type signature may be added:
let len :: Num a => a
len = genericLength xs
in (len, len)
对于这一点,我相信 wiki 中的例子更清楚。
考虑函数:
f xs = (len, len)
where
len = genericLength xs
如果 len
是多态的 f
的类型将是:
f :: Num a, Num b => [c] -> (a, b)
所以元组 (len, len)
的两个元素实际上可以是
不同 值!但这意味着 genericLength
完成的计算
必须重复才能获得两个不同的值。
这里的理由是:代码包含一个函数调用,但没有介绍
此规则可能会产生 两个 隐藏函数调用,这是违反直觉的。
有了单态限制,f
的类型变成:
f :: Num a => [b] -> (a, a)
这样就不需要多次计算了
Rule 1 prevents ambiguity. For example, consider the declaration group
[(n,s)] = reads t
Recall that reads
is a standard function whose type is given by the signature
reads :: (Read a) => String -> [(a,String)]
Without Rule 1, n
would be assigned the type ∀ a. Read a ⇒ a
and s
the type ∀ a. Read a ⇒ String
.
The latter is an invalid type, because it is inherently ambiguous.
It is not possible to determine at what overloading to use s
,
nor can this be solved by adding a type signature for s
.
Hence, when non-simple pattern bindings are used (Section 4.4.3.2 ),
the types inferred are always monomorphic in their constrained type variables,
irrespective of whether a type signature is provided.
In this case, both n
and s
are monomorphic in a
.
嗯,我相信这个例子是self-explanatory。有些情况下不
应用该规则会导致类型歧义。
如果您按照上面的建议禁用扩展程序,您 将 收到类型错误
试图编译上述声明。然而,这并不是真正的问题:
你已经知道在使用 read
时你必须以某种方式告诉编译器
它应该尝试解析哪种类型...
第二条规则
- Any monomorphic type variables that remain when type inference for an
entire module is complete, are considered ambiguous, and are resolved
to particular types using the defaulting rules (Section 4.3.4 ).
这个意思。如果你有你通常的定义:
plus = (+)
这将有一个类型 Num a => a -> a -> a
其中 a
是一个
单态 类型变量由于上述规则 1。一旦整个模块
推断编译器将简单地选择一种类型来替换 a
根据默认规则。
最终结果为:plus :: Integer -> Integer -> Integer
.
请注意,这是在 推断出整个模块之后完成的。
这意味着如果您有以下声明:
plus = (+)
x = plus 1.0 2.0
在模块中,before 类型默认 plus
的类型将是:
Fractional a => a -> a -> a
(请参阅规则 1 了解发生这种情况的原因)。
此时,按照默认规则,a
将被替换为 Double
所以我们将有 plus :: Double -> Double -> Double
和 x :: Double
.
默认
如前所述,存在一些 默认 规则,在 Section 4.3.4 of the Report 中有所描述,
推理器可以采用,并将用单态类型替换多态类型。
只要类型 不明确 .
就会发生这种情况
例如在表达式中:
let x = read "<something>" in show x
这里的表达式不明确,因为 show
和 read
的类型是:
show :: Show a => a -> String
read :: Read a => String -> a
所以 x
的类型是 Read a => a
。但是很多类型都满足这个约束:
例如 Int
、Double
或 ()
。选择哪一个?没有什么可以告诉我们的。
在这种情况下,我们可以通过告诉编译器我们想要哪种类型来解决歧义,
添加类型签名:
let x = read "<something>" :: Int in show x
现在的问题是:由于 Haskell 使用 Num
类型 class 来处理数字,
有很多 数值表达式包含歧义的情况。
考虑:
show 1
结果应该是什么?
和以前一样 1
有类型 Num a => a
并且可以使用许多类型的数字。
选择哪一个?
几乎每次我们使用数字时都会出现编译器错误并不是一件好事,
因此引入了违约规则。规则可控
使用 default
声明。通过指定 default (T1, T2, T3)
我们可以改变
推理器如何默认不同类型。
一个不明确的类型变量 v
是默认的,如果:
v
仅出现在 C v
类型的约束中 C
是 class
(即,如果它显示为:Monad (m v)
那么它是 不可 可默认的。
- 这些 class 中至少有一个是
Num
或 Num
的子class。
- 所有这些 class 都在 Prelude 或标准库中定义。
默认类型变量被default
列表中的第一个类型替换
这是所有歧义变量的实例 classes.
默认的 default
声明是 default (Integer, Double)
。
例如:
plus = (+)
minus = (-)
x = plus 1.0 1
y = minus 2 1
推断的类型为:
plus :: Fractional a => a -> a -> a
minus :: Num a => a -> a -> a
默认规则变为:
plus :: Double -> Double -> Double
minus :: Integer -> Integer -> Integer
请注意,这解释了为什么在问题的示例中只有 sort
定义引发错误。类型 Ord a => [a] -> [a]
不能默认
因为 Ord
不是数字 class.
扩展默认设置
注意 GHCi 自带 extended defaulting rules (or here for GHC8),
也可以使用 ExtendedDefaultRules
扩展在文件中启用。
默认类型变量不需要只出现在所有的约束中
class 是标准的,必须至少有一个 class
Eq
、Ord
、Show
或 Num
及其子classes.
而且默认的 default
声明是 default ((), Integer, Double)
.
这可能会产生奇怪的结果。以问题为例:
Prelude> :set -XMonomorphismRestriction
Prelude> import Data.List(sortBy)
Prelude Data.List> let sort = sortBy compare
Prelude Data.List> :t sort
sort :: [()] -> [()]
在 ghci 中我们没有得到类型错误但是 Ord a
约束导致
默认值 ()
几乎没用。
有用的链接
关于单态限制有很多资源和讨论。
以下是一些我认为有用的链接,可以帮助您理解或深入了解该主题:
- Haskell 的维基页面:Monomorphism Restriction
- report
- 方便又漂亮blog post
- A History Of Haskell: Being Lazy With Class 的第 6.2 和 6.3 节处理单态限制和类型默认
我对 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
不能为sort
使用多态类型Ord a => [a] -> [a]
? - 为什么
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 中关于单态限制的规范问题 正如 [元问题](https://meta.whosebug.com/questions/294053/can-we-provide-a-canonical-questionanswer-for-haskells-monomorphism-restrictio) 中所讨论的那样。
什么是单态限制?
Haskell wiki 所述的 monomorphism restriction 是:
a counter-intuitive rule in Haskell type inference. If you forget to provide a type signature, sometimes this rule will fill the free type variables with specific types using "type defaulting" rules.
这意味着,在某些情况下,如果您的类型不明确(即多态) 编译器将选择 实例化 该类型为明确的类型。
我该如何解决?
首先,您始终可以明确提供类型签名,这将 避免触发限制:
plus :: Num a => a -> a -> a
plus = (+) -- Okay!
-- Runs as:
Prelude> plus 1.0 1
2.0
请注意,只有变量上的普通类型签名才算作此目的,表达式类型签名不算数。例如,这样写仍然会导致限制被触发:
plus = (+) :: Num a => a -> a -> a
或者,如果你正在定义一个函数,你可以避免 point-free style, 例如写:
plus x y = x + y
正在关闭
可以简单地关闭限制,这样你就不必做
任何你的代码来修复它。该行为由两个扩展控制:
MonomorphismRestriction
将启用它(这是默认设置),同时
NoMonomorphismRestriction
将禁用它。
您可以将以下行放在文件的最顶部:
{-# LANGUAGE NoMonomorphismRestriction #-}
如果您使用的是 GHCi,则可以使用 :set
命令启用扩展:
Prelude> :set -XNoMonomorphismRestriction
您还可以告诉 ghc
从命令行启用扩展:
ghc ... -XNoMonomorphismRestriction
注意:您应该更喜欢第一个选项,而不是通过 command-line 选项选择扩展。
请参阅 GHC's page 了解此扩展和其他扩展的解释。
完整的解释
我将尝试在下面总结您需要了解的所有内容,以了解 单态限制是,为什么引入它以及它的行为方式。
一个例子
采用以下简单的定义:
plus = (+)
您认为能够用 plus
替换每次出现的 +
。特别是因为 (+) :: Num a => a -> a -> a
你期望也有 plus :: Num a => a -> a -> a
.
不幸的是,情况并非如此。例如,如果我们在 GHCi 中尝试以下操作:
Prelude> let plus = (+)
Prelude> plus 1.0 1
我们得到以下输出:
<interactive>:4:6:
No instance for (Fractional Integer) arising from the literal ‘1.0’
In the first argument of ‘plus’, namely ‘1.0’
In the expression: plus 1.0 1
In an equation for ‘it’: it = plus 1.0 1
您可能需要 :set -XMonomorphismRestriction
在较新的 GHCi 版本中。
事实上我们可以看到 plus
的类型不是我们所期望的:
Prelude> :t plus
plus :: Integer -> Integer -> Integer
发生的事情是编译器发现 plus
具有类型 Num a => a -> a -> a
,一个多态类型。
此外,碰巧上面的定义属于我稍后将解释的规则,所以
他决定通过 默认 类型变量 a
使类型成为单态的。
正如我们所见,默认值为 Integer
。
请注意,如果您尝试使用 ghc
编译 上述代码,您将不会收到任何错误。
这是由于 ghci
处理(并且 必须 处理)交互式定义的方式。
基本上在 ghci
中输入的每个语句都必须 完全 之前进行类型检查
考虑以下因素;换句话说,就好像每个语句都在一个单独的
模块。稍后我会解释为什么这很重要。
其他一些例子
考虑以下定义:
f1 x = show x
f2 = \x -> show x
f3 :: (Show a) => a -> String
f3 = \x -> show x
f4 = show
f5 :: (Show a) => a -> String
f5 = show
我们希望所有这些函数都以相同的方式运行并具有相同的类型,
即 show
的类型:Show a => a -> String
.
然而,在编译上述定义时,我们得到以下错误:
test.hs:3:12:
No instance for (Show a1) arising from a use of ‘show’
The type variable ‘a1’ is ambiguous
Relevant bindings include
x :: a1 (bound at blah.hs:3:7)
f2 :: a1 -> String (bound at blah.hs:3:1)
Note: there are several potential instances:
instance Show Double -- Defined in ‘GHC.Float’
instance Show Float -- Defined in ‘GHC.Float’
instance (Integral a, Show a) => Show (GHC.Real.Ratio a)
-- Defined in ‘GHC.Real’
...plus 24 others
In the expression: show x
In the expression: \ x -> show x
In an equation for ‘f2’: f2 = \ x -> show x
test.hs:8:6:
No instance for (Show a0) arising from a use of ‘show’
The type variable ‘a0’ is ambiguous
Relevant bindings include f4 :: a0 -> String (bound at blah.hs:8:1)
Note: there are several potential instances:
instance Show Double -- Defined in ‘GHC.Float’
instance Show Float -- Defined in ‘GHC.Float’
instance (Integral a, Show a) => Show (GHC.Real.Ratio a)
-- Defined in ‘GHC.Real’
...plus 24 others
In the expression: show
In an equation for ‘f4’: f4 = show
所以 f2
和 f4
不编译。此外,在尝试定义这些功能时
在 GHCi 中,我们得到 没有错误 ,但是 f2
和 f4
的类型是 () -> String
!
单态限制使得 f2
和 f4
需要单态
类型,ghc
和 ghci
之间的不同行为是由于不同的
默认规则。
什么时候发生?
在Haskell中定义为report, there are two distinct type of bindings。 函数绑定和模式绑定。函数绑定无非就是 一个函数的定义:
f x = x + 1
注意他们的语法是:
<identifier> arg1 arg2 ... argn = expr
模守卫和where
声明。但它们并不重要。
其中必须有至少一个参数。
模式绑定是以下形式的声明:
<pattern> = expr
同样,模守卫。
注意变量是模式,所以绑定:
plus = (+)
是一个模式绑定。它将模式 plus
(一个变量)绑定到表达式 (+)
.
当模式绑定只包含一个变量名时,它被称为 简单 模式绑定。
单态限制适用于简单模式绑定!
嗯,正式地我们应该这样说:
的第 4.5.1 节A declaration group is a minimal set of mutually dependent bindings.
然后(report 的第 4.5.5 节):
a given declaration group is unrestricted if and only if:
every variable in the group is bound by a function binding (e.g.
f x = x
) or a simple pattern binding (e.g.plus = (+)
; Section 4.4.3.2 ), andan explicit type signature is given for every variable in the group that is bound by simple pattern binding. (e.g.
plus :: Num a => a -> a -> a; plus = (+)
).
我加的例子
所以 restricted 声明组是一个组,其中要么
non-simple 模式绑定(例如 (x:xs) = f something
或 (f, g) = ((+), (-))
)或
有一些没有类型签名的简单模式绑定(如 plus = (+)
)。
单态限制影响限制声明组。
大多数时候你没有定义相互递归函数,因此没有声明 组变成 a 绑定。
它有什么作用?
单态性限制由部分中的两个规则描述 report.
的4.5.5第一条规则
The usual Hindley-Milner restriction on polymorphism is that only type variables that do not occur free in the environment may be generalized. In addition, the constrained type variables of a restricted declaration group may not be generalized in the generalization step for that group. (Recall that a type variable is constrained if it must belong to some type class; see Section 4.5.2 .)
突出显示的部分是单态限制引入的内容。
它说 if 类型是多态的(即它包含一些类型变量)
和 类型变量受到约束(即它具有 class 约束:
例如Num a => a -> a -> a
类型是多态的,因为它包含 a
和
也受到约束,因为 a
对其有约束 Num
。)
那么不能一概而论
简单来说不概括意味着函数plus
的使用可能会改变它的类型。
如果你有定义:
plus = (+)
x :: Integer
x = plus 1 2
y :: Double
y = plus 1.0 2
然后你会得到一个类型错误。因为当编译器看到 plus
是
在 x
的声明中调用 Integer
它将统一类型
变量 a
与 Integer
因此 plus
的类型变为:
Integer -> Integer -> Integer
但是,当它检查 y
的定义时,它会看到 plus
应用于 Double
参数,但类型不匹配。
请注意,您仍然可以使用 plus
而不会出现错误:
plus = (+)
x = plus 1.0 2
在这种情况下,plus
的类型首先被推断为 Num a => a -> a -> a
但随后它在 x
的定义中使用,其中 1.0
需要一个 Fractional
约束,将其更改为 Fractional a => a -> a -> a
.
理由
报告说:
Rule 1 is required for two reasons, both of which are fairly subtle.
Rule 1 prevents computations from being unexpectedly repeated. For example,
genericLength
is a standard function (in libraryData.List
) whose type is given bygenericLength :: Num a => [b] -> a
Now consider the following expression:
let len = genericLength xs in (len, len)
It looks as if
len
should be computed only once, but without Rule 1 it might be computed twice, once at each of two different overloadings. If the programmer does actually wish the computation to be repeated, an explicit type signature may be added:let len :: Num a => a len = genericLength xs in (len, len)
对于这一点,我相信 wiki 中的例子更清楚。 考虑函数:
f xs = (len, len)
where
len = genericLength xs
如果 len
是多态的 f
的类型将是:
f :: Num a, Num b => [c] -> (a, b)
所以元组 (len, len)
的两个元素实际上可以是
不同 值!但这意味着 genericLength
完成的计算
必须重复才能获得两个不同的值。
这里的理由是:代码包含一个函数调用,但没有介绍 此规则可能会产生 两个 隐藏函数调用,这是违反直觉的。
有了单态限制,f
的类型变成:
f :: Num a => [b] -> (a, a)
这样就不需要多次计算了
Rule 1 prevents ambiguity. For example, consider the declaration group
[(n,s)] = reads t
Recall that
reads
is a standard function whose type is given by the signaturereads :: (Read a) => String -> [(a,String)]
Without Rule 1,
n
would be assigned the type∀ a. Read a ⇒ a
ands
the type∀ a. Read a ⇒ String
. The latter is an invalid type, because it is inherently ambiguous. It is not possible to determine at what overloading to uses
, nor can this be solved by adding a type signature fors
. Hence, when non-simple pattern bindings are used (Section 4.4.3.2 ), the types inferred are always monomorphic in their constrained type variables, irrespective of whether a type signature is provided. In this case, bothn
ands
are monomorphic ina
.
嗯,我相信这个例子是self-explanatory。有些情况下不 应用该规则会导致类型歧义。
如果您按照上面的建议禁用扩展程序,您 将 收到类型错误
试图编译上述声明。然而,这并不是真正的问题:
你已经知道在使用 read
时你必须以某种方式告诉编译器
它应该尝试解析哪种类型...
第二条规则
- Any monomorphic type variables that remain when type inference for an entire module is complete, are considered ambiguous, and are resolved to particular types using the defaulting rules (Section 4.3.4 ).
这个意思。如果你有你通常的定义:
plus = (+)
这将有一个类型 Num a => a -> a -> a
其中 a
是一个
单态 类型变量由于上述规则 1。一旦整个模块
推断编译器将简单地选择一种类型来替换 a
根据默认规则。
最终结果为:plus :: Integer -> Integer -> Integer
.
请注意,这是在 推断出整个模块之后完成的。
这意味着如果您有以下声明:
plus = (+)
x = plus 1.0 2.0
在模块中,before 类型默认 plus
的类型将是:
Fractional a => a -> a -> a
(请参阅规则 1 了解发生这种情况的原因)。
此时,按照默认规则,a
将被替换为 Double
所以我们将有 plus :: Double -> Double -> Double
和 x :: Double
.
默认
如前所述,存在一些 默认 规则,在 Section 4.3.4 of the Report 中有所描述, 推理器可以采用,并将用单态类型替换多态类型。 只要类型 不明确 .
就会发生这种情况例如在表达式中:
let x = read "<something>" in show x
这里的表达式不明确,因为 show
和 read
的类型是:
show :: Show a => a -> String
read :: Read a => String -> a
所以 x
的类型是 Read a => a
。但是很多类型都满足这个约束:
例如 Int
、Double
或 ()
。选择哪一个?没有什么可以告诉我们的。
在这种情况下,我们可以通过告诉编译器我们想要哪种类型来解决歧义, 添加类型签名:
let x = read "<something>" :: Int in show x
现在的问题是:由于 Haskell 使用 Num
类型 class 来处理数字,
有很多 数值表达式包含歧义的情况。
考虑:
show 1
结果应该是什么?
和以前一样 1
有类型 Num a => a
并且可以使用许多类型的数字。
选择哪一个?
几乎每次我们使用数字时都会出现编译器错误并不是一件好事,
因此引入了违约规则。规则可控
使用 default
声明。通过指定 default (T1, T2, T3)
我们可以改变
推理器如何默认不同类型。
一个不明确的类型变量 v
是默认的,如果:
v
仅出现在C v
类型的约束中C
是 class (即,如果它显示为:Monad (m v)
那么它是 不可 可默认的。- 这些 class 中至少有一个是
Num
或Num
的子class。 - 所有这些 class 都在 Prelude 或标准库中定义。
默认类型变量被default
列表中的第一个类型替换
这是所有歧义变量的实例 classes.
默认的 default
声明是 default (Integer, Double)
。
例如:
plus = (+)
minus = (-)
x = plus 1.0 1
y = minus 2 1
推断的类型为:
plus :: Fractional a => a -> a -> a
minus :: Num a => a -> a -> a
默认规则变为:
plus :: Double -> Double -> Double
minus :: Integer -> Integer -> Integer
请注意,这解释了为什么在问题的示例中只有 sort
定义引发错误。类型 Ord a => [a] -> [a]
不能默认
因为 Ord
不是数字 class.
扩展默认设置
注意 GHCi 自带 extended defaulting rules (or here for GHC8),
也可以使用 ExtendedDefaultRules
扩展在文件中启用。
默认类型变量不需要只出现在所有的约束中
class 是标准的,必须至少有一个 class
Eq
、Ord
、Show
或 Num
及其子classes.
而且默认的 default
声明是 default ((), Integer, Double)
.
这可能会产生奇怪的结果。以问题为例:
Prelude> :set -XMonomorphismRestriction
Prelude> import Data.List(sortBy)
Prelude Data.List> let sort = sortBy compare
Prelude Data.List> :t sort
sort :: [()] -> [()]
在 ghci 中我们没有得到类型错误但是 Ord a
约束导致
默认值 ()
几乎没用。
有用的链接
关于单态限制有很多资源和讨论。
以下是一些我认为有用的链接,可以帮助您理解或深入了解该主题:
- Haskell 的维基页面:Monomorphism Restriction
- report
- 方便又漂亮blog post
- A History Of Haskell: Being Lazy With Class 的第 6.2 和 6.3 节处理单态限制和类型默认