什么是单态限制?

我很困惑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对待plusplus'有所不同? plus应该有多态typesNum a => a -> a -> a ,我真的不知道这与sorttypes有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 

发生了什么事情是编译器看到plusNum a => a -> a -> a ,一个多态types。 此外,上述定义属于我将在后面解释的规则,因此他决定通过默认typesvariablesa来使types成为单态。 我们可以看到默认值是Integer

请注意,如果您尝试使用ghc 编译上面的代码,您将不会收到任何错误。 这是由于ghci如何处理(并且必须处理)交互式定义。 基本上,每一个在ghciinput的语句在进行下面的考虑之前都必须进行完整的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 

所以f2f4不能编译。 此外,当试图在GHCi中定义这些函数时,我们没有得到任何错误 ,但是f2f4的types是() -> String

单态的约束是f2f4需要一个单形types,而ghcghci的不同行为是由于不同的违约规则

什么时候发生?

在报告中定义的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节):

给定的声明组是不受限制的,当且仅当:

  1. 组中的每个variables都被一个函数绑定(例如fx = x )或一个简单的模式绑定(例如, plus = (+) ;第4.4.3.2节)绑定,

  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时,它会将typesvariablesaInteger统一起来,因此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 ⇒ as的types∀ a. Read a ⇒ String ∀ a. Read a ⇒ String 。 后者是无效的types,因为它本质上是不明确的。 无法确定使用什么重载,也不能通过为s添加types签名来解决这个问题。 因此,当使用非简单模式绑定(见第4.4.3.2节)时,推断的types总是在它们的约束typesvariables中是单形的,而不pipe是否提供了types签名。 在这种情况下, ns都是单形a

那么,我相信这个例子是不言自明的。 有些情况下,不应用规则结果的types歧义。

如果您按照上面的build议禁用了扩展,那么当您尝试编译上述声明时将会出现types错误。 然而,这不是一个真正的问题:你已经知道,当使用read你不得不告诉编译器应该尝试parsing哪种types…

第二条规则

  1. 当整个模块的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 -> Doublex :: Double

违约

如前所述,“报告”第4.3.4节描述了一些违约规则,推理人可以采用并且将会取代单态的多态types。 这种情况发生在types不明确时

例如在expression式中:

 let x = read "<something>" in show x 

这里的expression是不明确的,因为showread的types是:

 show :: Show a => a -> String read :: Read a => String -> a 

所以xtypes是Read a => a 。 但是这个约束被许多types满足:例如IntDouble() 。 哪一个select? 没有什么可以告诉我们的。

在这种情况下,我们可以通过告诉编译器告诉编译器我们需要哪种types来添加types签名来解决模糊问题:

 let x = read "<something>" :: Int in show x 

现在的问题是:因为Haskell使用Numtypes来处理数字,所以有很多情况下数字expression式含有歧义。

考虑:

 show 1 

结果应该是什么?

如前所述1有typesNum a => a并且可以使用许多types的数字。 哪一个select?

几乎每次我们使用一个数字都有一个编译器错误不是一件好事,因此引入了违约规则。 规则可以使用default声明进行控制。 通过指定default (T1, T2, T3)我们可以改变推理器如何默认不同的types。

如果满足以下条件,模糊的typesvariablesv是可以默认的:

  • v只出现在C vC是一个类(即,如果它出现在: Monad (mv)那么它不是可指定的 )。
  • 这些类中至less有一个是NumNum的子类。
  • 所有这些类都是在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不仅需要在所有类都是标准的约束条件下出现,并且在EqOrdShowNum及其子类中至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违约
Interesting Posts