什么是单态限制?
我很困惑haskell编译器如何推断比我所期望的多态性更less的types,例如,当使用无点定义时。
看起来问题是“单态限制”,在编译器的旧版本上默认是这样的。
考虑下面的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
编译这个,我没有得到erros,可执行文件的输出是:
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]
我得到一个types错误:
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
尝试使用不同types调用两次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
不能使用多态typesOrd a => [a] -> [a]
进行sort
? - 为什么
ghc
对待plus
和plus'
有所不同?plus
应该有多态typesNum a => a -> a -> a
,我真的不知道这与sort
types有sort
,但是只有sort
会产生一个错误。
最后一件事:如果我评论sort
文件的定义编译。 但是,如果我尝试加载到ghci
并检查我得到的types:
*TestMono> :t plus plus :: Integer -> Integer -> Integer *TestMono> :t plus' plus' :: Num a => a -> a -> a
为什么不是plus
态的types?
这是在元问题中讨论的有关Haskell单形态限制的典型问题 。
什么是单态限制?
Haskell wiki的单态限制是:
Haskelltypes推断中的反直觉规则。 如果你忘记提供一个types签名,有时这个规则将使用“types默认”规则填充特定types的自由typesvariables。
这意味着, 在某些情况下 ,如果你的types不明确(即多态),编译器会select将这个types实例化为不含糊的东西。
我如何解决它?
首先,你总是可以明确地提供一个types签名,这将避免触发限制:
plus :: Num a => a -> a -> a plus = (+) -- Okay! -- Runs as: Prelude> plus 1.0 1 2.0
另外,如果你正在定义一个函数,你可以避免无 点式的风格 ,例如写:
plus xy = x + y
把它关掉
可以简单地closures这个限制,这样你就不需要对代码做任何修改。 该行为由两个扩展控制: MonomorphismRestriction
将启用它(这是默认值),而NoMonomorphismRestriction
将禁用它。
您可以将以下行放在文件的最上面:
{-# LANGUAGE NoMonomorphismRestriction #-}
如果您正在使用GHCi,则可以使用:set
命令启用扩展:
Prelude> :set -XNoMonomorphismRestriction
你也可以告诉ghc
从命令行启用扩展:
ghc ... -XNoMonomorphismRestriction
注意:您应该首选第一个选项,而不是通过命令行选项select扩展名。
请参阅GHC的页面以获取对此扩展和其他扩展的解释。
完整的解释
我将在下面总结一下你需要知道的一切,以便了解单态限制是什么,为什么被引入以及如何performance。
一个例子
采取以下简单的定义:
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版本中:set -XMonomorphismRestriction
。
事实上,我们可以看到, plus
的types并不是我们所期望的:
Prelude> :t plus plus :: Integer -> Integer -> Integer
发生了什么事情是编译器看到plus
有Num a => a -> a -> a
,一个多态types。 此外,上述定义属于我将在后面解释的规则,因此他决定通过默认typesvariablesa
来使types成为单态。 我们可以看到默认值是Integer
。
请注意,如果您尝试使用ghc
编译上面的代码,您将不会收到任何错误。 这是由于ghci
如何处理(并且必须处理)交互式定义。 基本上,每一个在ghci
input的语句在进行下面的考虑之前都必须进行完整的types检查。 换句话说,就好像每个陈述都在一个单独的模块中一样 。 稍后我会解释为什么这个问题。
另外一些例子
考虑以下定义:
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
我们期望所有这些函数的行为方式相同,并且具有相同的types,即show
的types: 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
的types是() -> String
!
单态的约束是f2
和f4
需要一个单形types,而ghc
和ghci
的不同行为是由于不同的违约规则 。
什么时候发生?
在报告中定义的Haskell中,有两种不同types的绑定 。 函数绑定和模式绑定。 函数绑定只不过是一个函数的定义:
fx = x + 1
请注意,它们的语法是:
<identifier> arg1 arg2 ... argn = expr
Modulo卫兵和where
申报。 但他们并不重要。
必须至less有一个论点 。
模式绑定是一种forms的声明:
<pattern> = expr
再次,模数守卫。
请注意, variables是模式 ,所以绑定:
plus = (+)
是一种模式绑定。 它将模式plus
(variables)绑定到expression式(+)
。
当一个模式绑定只包含一个variables名称时,它被称为简单模式绑定。
单态限制适用于简单的模式绑定!
那么,我们应该正式说:
一个声明组是一组最小的相互依赖的绑定。
报告第4.5.1节。
然后( 报告第4.5.5节):
给定的声明组是不受限制的,当且仅当:
组中的每个variables都被一个函数绑定(例如
fx = x
)或一个简单的模式绑定(例如,plus = (+)
;第4.4.3.2节)绑定,对于由简单模式绑定绑定的组中的每个variables给出显式types签名。 (例如,
plus :: Num a => a -> a -> a; plus = (+)
)。
我添加的例子。
所以,一个受限制的声明组是一个组,其中有非简单模式绑定(例如(x:xs) = f something
或(f, g) = ((+), (-))
),或者有一些简单的没有types签名的模式绑定(如在plus = (+)
)。
单态限制影响限制性申报组。
大多数情况下,你没有定义相互recursion函数,因此一个声明组成为一个绑定。
它有什么作用?
报告第4.5.5节中的两个规则描述了单态限制。
第一条规则
通常的Hindley-Milner对多态性的限制是只有在环境中不能自由出现的typesvariables可以被推广。 另外, 受限声明组的约束型variables在该组的泛化步骤中可能不被推广。 (回想一下,如果一个typesvariables必须属于某个typestypes,那么这个typesvariables是受约束的;参见4.5.2节)
突出部分是单态限制引入的内容。 它说, 如果types是多态的(即它包含一些typesvariables), 并且该typesvariables是受约束的(即它有一个类约束:例如typesNum a => a -> a -> a
是多态的,因为它包含a
也是有约束的,因为a
具有Num
的约束), 那么它就不能被泛化。
简而言之, 不泛化意味着函数plus
的使用可能会改变它的types。
如果你有定义:
plus = (+) x :: Integer x = plus 1 2 y :: Double y = plus 1.0 2
那么你会得到一个types错误。 因为当编译器看到在x
的声明中通过Integer
调用plus
时,它会将typesvariablesa
与Integer
统一起来,因此plus
的types变成:
Integer -> Integer -> Integer
但是,当它将键入检查y
的定义时,它会看到plus
应用于Double
参数,并且types不匹配。
请注意,您仍然可以使用plus
而不会出现错误:
plus = (+) x = plus 1.0 2
在这种情况下, plus
的types首先被推断为Num a => a -> a -> a
,然后在x
的定义中使用它,其中1.0
需要Fractional
约束,将它改为Fractional a => a -> a -> a
。
合理
报告说:
规则1是必要的,原因有两个,都是相当微妙的。
规则1防止计算意外重复。 例如,
genericLength
是一个标准函数(在库Data.List
),其types由给定genericLength :: Num a => [b] -> a
现在考虑下面的expression式:
let len = genericLength xs in (len, len)
它看起来应该只计算一次
len
,但是如果没有规则1,它可能会被计算两次,一次在两个不同的重载中。 如果程序员真的希望重复计算,可以添加一个明确的types签名:let len :: Num a => a len = genericLength xs in (len, len)
对于这一点,我相信, 维基的例子更清楚。 考虑一下function:
f xs = (len, len) where len = genericLength xs
如果len
是多态的,那么f
的types将是:
f :: Num a, Num b => [c] -> (a, b)
所以元组(len, len)
的两个元素实际上可以是不同的值! 但这意味着genericLength
所做的计算必须重复以获得两个不同的值。
这里的基本原理是:代码包含一个函数调用,但不引入这个规则可能产生两个隐藏的函数调用,这是违反直觉的。
随着单态限制, f
的types变成:
f :: Num a => [b] -> (a, a)
这样就不需要多次执行计算。
规则1防止含糊不清。 例如,考虑申报组
[(n,s)] =读取t
回想一下,
reads
是一个标准函数,其types由签名给出读取::(读取一个)=>string – > [(a,string)]
没有规则1,
n
将被分配types∀ a. Read a ⇒ a
∀ a. Read a ⇒ a
和s
的types∀ a. Read a ⇒ String
∀ a. Read a ⇒ String
。 后者是无效的types,因为它本质上是不明确的。 无法确定使用什么重载,也不能通过为s
添加types签名来解决这个问题。 因此,当使用非简单模式绑定(见第4.4.3.2节)时,推断的types总是在它们的约束typesvariables中是单形的,而不pipe是否提供了types签名。 在这种情况下,n
和s
都是单形a
。
那么,我相信这个例子是不言自明的。 有些情况下,不应用规则结果的types歧义。
如果您按照上面的build议禁用了扩展,那么当您尝试编译上述声明时将会出现types错误。 然而,这不是一个真正的问题:你已经知道,当使用read
你不得不告诉编译器应该尝试parsing哪种types…
第二条规则
- 当整个模块的types推断完成时,任何单形态types的variables都被认为是不明确的,并且使用默认规则parsing为特定types(见4.3.4节)。
这意味着。 如果你有你惯常的定义:
plus = (+)
这将具有typesNum a => a -> a -> a
其中由于上述规则1, a
是单形型variables。 一旦推断出整个模块,编译器将简单地select一种types,根据默认规则来取代a
。
最后的结果是: plus :: Integer -> Integer -> Integer
。
请注意,这是在整个模块被推断之后完成的。
这意味着如果您有以下声明:
plus = (+) x = plus 1.0 2.0
在模块内部, 在默认types之前 , plus
的types是: Fractional a => a -> a -> a
(见规则1为什么发生这种情况)。 此时,遵循默认规则, a
将被replace为Double
,所以我们将有plus :: Double -> Double -> Double
和x :: Double
。
违约
如前所述,“报告”第4.3.4节描述了一些违约规则,推理人可以采用并且将会取代单态的多态types。 这种情况发生在types不明确时 。
例如在expression式中:
let x = read "<something>" in show x
这里的expression是不明确的,因为show
和read
的types是:
show :: Show a => a -> String read :: Read a => String -> a
所以x
types是Read a => a
。 但是这个约束被许多types满足:例如Int
, Double
或()
。 哪一个select? 没有什么可以告诉我们的。
在这种情况下,我们可以通过告诉编译器告诉编译器我们需要哪种types来添加types签名来解决模糊问题:
let x = read "<something>" :: Int in show x
现在的问题是:因为Haskell使用Num
types来处理数字,所以有很多情况下数字expression式含有歧义。
考虑:
show 1
结果应该是什么?
如前所述1
有typesNum a => a
并且可以使用许多types的数字。 哪一个select?
几乎每次我们使用一个数字都有一个编译器错误不是一件好事,因此引入了违约规则。 规则可以使用default
声明进行控制。 通过指定default (T1, T2, T3)
我们可以改变推理器如何默认不同的types。
如果满足以下条件,模糊的typesvariablesv
是可以默认的:
-
v
只出现在C v
中C
是一个类(即,如果它出现在:Monad (mv)
那么它不是可指定的 )。 - 这些类中至less有一个是
Num
或Num
的子类。 - 所有这些类都是在Prelude或标准库中定义的。
默认typesvariables被default
列表中的第一个typesreplace,该列表是所有不明确variables的类的实例。
默认的default
声明是default (Integer, Double)
。
例如:
plus = (+) minus = (-) x = plus 1.0 1 y = minus 2 1
推断的types是:
plus :: Fractional a => a -> a -> a minus :: Num a => a -> a -> a
根据违约规则,这些规则成为:
plus :: Double -> Double -> Double minus :: Integer -> Integer -> Integer
请注意,这就解释了为什么在问题的例子中,只有sort
定义会引发错误。 typesOrd a => [a] -> [a]
不能被默认,因为Ord
不是一个数字类。
延期违约
请注意,GHCi带有扩展的默认规则 (或GHC8 ),可以使用ExtendedDefaultRules
扩展在文件中启用它。
可违约typesvariables不仅需要在所有类都是标准的约束条件下出现,并且在Eq
, Ord
, Show
或Num
及其子类中至less有一个类。
而且默认的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中,我们没有得到types错误,但Ord a
约束导致默认的()
,这是非常无用的。
有用的链接
关于单态限制有很多资源和讨论。
以下是我认为有用的一些链接,这可能有助于您理解或深入探讨该主题:
- Haskell的维基页面: Monomorphism Restriction
- 报告
- 一个可访问和不错的博客文章
- 哈斯克尔历史第6.2节和第6.3节:懒惰与类处理单态限制和types违约