为了账号安全,请及时绑定邮箱和手机立即绑定

一元限制是什么?

/ 猿问

一元限制是什么?

一元限制是什么?

我很困惑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.03.0[1,2,3]

如果我更改main尸体:

main = do
  print $ plus' 1.0 2.0
  print $ plus (1 :: Int) 2
  print $ sort [3, 1, 2]

我没有编译时错误,输出变成:

3.03[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


产生以下错误:

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

    举个例子?
编译时会出现以下错误:
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

    引发错误。



查看完整描述

1 回答

?
不负相思意

一元限制是什么?

这个单态约束正如Haskell wiki所指出的那样:

Haskell型推理中的一条违反直觉的规则。如果您忘记提供类型签名,有时此规则将使用“类型默认”规则使用特定类型填充空闲类型变量。

这意味着在某些情况下,如果您的类型不明确(即多态),编译器将选择实例化那种类型的东西不含糊。

我该怎么解决呢?

首先,您可以始终显式提供类型签名,这将避免触发限制:

plus :: Num a => a -> a -> a
plus = (+)    -- Okay!-- Runs as:Prelude> plus 1.0 12.0

或者,如果要定义函数,则可以 无分风格,例如,写:

plus x y = x + y

关掉它

可以简单地关闭限制,这样您就不必对代码做任何事情来修复它。这种行为由两个扩展控制:MonomorphismRestriction将启用它(这是默认的),而NoMonomorphismRestriction会使它失效。

您可以将以下行放在文件的最上面:

{-# LANGUAGE NoMonomorphismRestriction #-}

如果使用的是GHCi,则可以使用:set指挥:

Prelude> :set -XNoMonomorphismRestriction

你也可以看出ghc要从命令行启用扩展:

ghc ... -XNoMonomorphismRestriction

注:与通过命令行选项选择扩展相比,您更喜欢第一个选项。

请参阅GHC页面有关此扩展和其他扩展的说明。

完整的解释

我将尝试在下面总结您需要了解的一切,以了解单态限制是什么,为什么引入它,以及它的行为。

一个例子

采用以下简单的定义:

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

所发生的是编译器看到plusHd型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

我们期望所有这些函数都以相同的方式运行,并且具有相同的类型,即showShow 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() -> String!

单态限制是造成f2f4需要一个单一的类型,和不同的行为ghcghci是因为不同违约规则.

什么时候会发生?

在Haskell中,由报告,有不同类型的绑定..函数绑定和模式绑定。函数绑定只不过是函数的定义:

f x = x + 1

请注意,它们的语法是:

<identifier> arg1 arg2 ... argn = expr

护卫和where声明。但它们并不重要。

那里一定有至少有一个论点.

模式绑定是表单的声明:

<pattern> = expr

再来一次模块化警卫。

请注意变量是模式,所以约束:

plus = (+)

花纹绑定。它结合了模式plus(变量)的表达式(+).

当模式绑定仅由一个变量名组成时,它被称为简约模式绑定

单态限制适用于简单的模式绑定!

那么,从形式上讲,我们应该说:

声明组是一组相互依赖的最小绑定。

条例第4.5.1节报告.

然后(“公约”第4.5.5条)报告):

给定的声明组是无限制当且仅当:

  1. 组中的每个变量都受函数绑定的约束(例如:f x = x)或简单的模式绑定(例如:plus = (+)(第4.4.3.2节)

  2. 为组中受简单模式绑定约束的每个变量提供显式类型签名。(如:plus :: Num a => a -> a -> a; plus = (+)).

我补充的例子。

所以受限声明组是一个组,其中任何一个非简单模式绑定(例如:(x:xs) = f something(f, g) = ((+), (-)))或者有一些没有类型签名的简单模式绑定(如plus = (+)).

单态约束受限申报组。

大多数情况下,您没有定义相互递归函数,因此声明组就变成了a绑定。

是干什么的呢?

第4.5.5节中的两个规则描述了单态约束报告.

第一条规则

通常对多态的辛德雷-米尔纳限制是,只有在环境中不自由出现的类型变量才能被泛化。此外,受限声明组的约束类型变量在该组的泛化步骤中不可能被泛化。(回想一下,如果一个类型变量必须属于某个类型类,它就会受到约束;参见4.5.2节。)

高亮部分是单形限制引入的内容。上面说如果类型是多态的(即它包含一些类型变量)该类型变量受到约束(即它有一个类约束:例如,类型Num a => a -> a -> a是多态的,因为它包含a也是因为a有约束Num)然后它不能概括。

用简单的话来说不概括意味着使用职能plus可能会改变它的类型。

如果您有以下定义:

plus = (+)x :: Integer
x = plus 1 2y :: Double
y = plus 1.0 2

那你就会得到一个类型错误。因为当编译器看到plus被调用为Integer在声明中x它将统一类型变量。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.

理据

报告说:

规则1之所以需要,有两个原因,这两个理由都相当微妙。

  • 规则1防止意外重复计算。例如,genericLength是一个标准函数(在图书馆中)。Data.List)其类型由

    genericLength :: Num a => [b] -> a

    现在考虑以下表达式:

    let len = genericLength xsin (len, len)

    看起来好像len应该只计算一次,但是如果没有规则1,它可以计算两次,一次在两个不同的重载。如果程序员确实希望重复计算,则可以添加显式类型签名:

    let len :: Num a => a
        len = genericLength xsin (len, len)

对于这一点,请参见维基我相信更清楚。考虑以下职能:

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)

这样就不需要多次执行计算。

  • 规则1防止模棱两可。例如,考虑声明组

    [(n,s)]=读t

    回想一下reads是一个标准函数,其类型由签名提供。

    读:(读a)=>string->[(a,string)]

    如果没有规则1,n将被分配给类型∀ a. Read a  ⇒  as类型∀ a. Read a ⇒  String..后者是一个无效类型,因为它本质上是模棱两可的。无法确定在什么超载时使用s,也不能通过添加类型签名来解决这一问题。s..因此,当使用非简单模式绑定时(第4.4.3.2节),无论是否提供类型签名,推断的类型在其约束类型变量中始终是单纯的。在这种情况下,两者ns单形a.

我相信这个例子是不言自明的。在某些情况下,不应用规则会导致类型歧义。

如果按照上面的建议禁用分机将要在试图编译上述声明时获取类型错误。然而,这并不是一个真正的问题:您已经知道当您使用read你必须告诉编译器它应该分析哪种类型.

第二规则

  1. 当整个模块的类型推断完成时,任何单一类型变量都被认为是模糊的,并且使用默认规则将其解析为特定类型(第4.3.4节)。

这意味着。如果你有你通常的定义:

plus = (+)

这将有一个类型Num a => a -> a -> a哪里a单形由于上面描述的规则1,输入变量。一旦推断出整个模块,编译器就会简单地选择一个类型来替换它。a根据违约规则。

最终结果是:plus :: Integer -> Integer -> Integer.

请注意,这已经完成了。推断出整个模块。

这意味着,如果您有以下声明:

plus = (+)x = plus 1.0 2.0

在一个模块里,以前类型的默认类型。plus将是:Fractional a => a -> a -> a(关于发生这种情况的原因,见规则1)。在这一点上,遵循默认规则,a将由Double所以我们会plus :: Double -> Double -> Doublex :: Double.

违约

如前所述,有一些违约规则,在报告第4.3.4节,这将取代多态类型的单一类型。每当类型为模棱两可.

例如,在表达式中:

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

在这里,表达式是模棱两可的,因为showread是:

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

所以x有型Read a => a..但是很多类型都满足了这个约束:IntDouble()例如。选哪一个?没什么能告诉我们的。

在这种情况下,我们可以通过告诉编译器我们想要哪种类型来解决歧义,添加类型签名:

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

现在的问题是:既然Haskell使用Num类来处理数字,有很多在数字表达式包含歧义的情况下。

考虑:

show 1

结果应该是什么?

像以前一样1有型Num a => a有许多类型的数字可以使用。选哪一个?

几乎每次使用数字时都会出现编译器错误,这不是件好事,因此引入了默认规则。规则可以使用default申报。通过指定default (T1, T2, T3)我们可以改变推理者默认不同类型的方式。

模棱两可的类型变量v如果:

  • v

    只出现在这类违禁品中

    C v

    都是

    C

    是一个类(如果它出现在:

    Monad (m v)

    那就是

    (可默认)。
  • 这些类中至少有一个是

    Num

    的子类

    Num.

  • 所有这些类都是在前奏曲或标准库中定义的。

类替换为默认类型变量。第一输入default列表,它是所有模糊变量的类的实例。

默认default声明是default (Integer, Double).

例如:

plus = (+)minus = (-)x = plus 1.0 1y = 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不是数字类。

扩展违约

请注意,GHCi附带扩展违约规则(或为GHC 8而来),也可以在文件中使用ExtendedDefaultRules延期。

默认类型变量不需要出现在违禁品中,其中所有类都是标准的,并且必须至少有一个类在其中。EqOrdShowNum及其子类。

此外,默认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约束导致默认的()这几乎毫无用处。

有用链接

确实有很多关于单态限制的讨论。

下面是一些我觉得有用的链接,可以帮助您理解或深入了解这个主题:


查看完整回答
反对 回复 2019-06-03
  • 1 回答
  • 0 关注
  • 65 浏览
我要回答
慕课专栏
更多

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信