Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/254.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Haskell 什么是单态限制?_Haskell_Types_Polymorphism_Type Inference_Monomorphism Restriction - Fatal编程技术网

Haskell 什么是单态限制?

Haskell 什么是单态限制?,haskell,types,polymorphism,type-inference,monomorphism-restriction,Haskell,Types,Polymorphism,Type Inference,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 prin

我对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]
如果我将主体更改为:

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

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]
我得到一个类型错误:

测试。hs:13:16: 没有由文字“1.0”生成的分数Int的实例 在“plus”的第一个参数中,即“1.0” 在“$”的第二个参数中,即“加1.0 2.0” 在“do”块的stmt中:打印$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"
产生以下错误:

测试。hs:14:17: 没有从文本“3”生成的Num Char实例 在表达式中:3 在'sort'的第一个参数中,即'[3,1,2]' 在“$”的第二个参数中,即“sort[3,1,2]” 为什么ghc突然认为plus不是多态的,需要一个Int参数? 对Int的唯一引用是在plus的应用程序中,这有什么关系 当定义明显是多态的时候? 为什么ghc突然认为排序需要Num Char实例? 此外,如果我尝试将函数定义放入它们自己的模块中,如:

{-# LANGUAGE MonomorphismRestriction #-}

module TestMono where

import Data.List(sortBy)

plus = (+)
plus' x = (+ x)

sort = sortBy compare
我在编译时遇到以下错误:

TestMono.hs:10:15: 没有因使用“比较”而产生的Ord a0实例 类型变量“a0”不明确 相关绑定包括 排序::[a0]->[a0]绑定在TestMono.hs:10:1 注意:有几个潜在的例子: 实例积分a=>Ord GHC.Real.Ratio a -在“GHC.Real”中定义 实例Ord-在“GHC.Classes”中定义 实例Ord a,Ord b=>Ord a,b-在“GHC.Classes”中定义 …加上其他23个 在'sortBy'的第一个参数中,即'compare' 在表达式中:sortBy compare 在“排序”的等式中:sort=sortBy compare 为什么ghc不能使用多态类型Ord a=>[a]->[a]进行排序? 为什么ghc对待plus和plus的方式不同?加上应该有 多态类型numa=>a->a->a,我真的看不出这有什么不同 从sort类型开始,但仅sort会引发错误。 最后一件事:如果我对文件编译的排序定义进行注释。然而 如果我尝试将其加载到ghci并检查得到的类型:

*TestMono>:t plus 整数->整数->整数 *TestMono>:t plus' 加上“::Num a=>a->a->a 为什么plus的类型不是多态的

这是关于Haskell中单态限制的规范问题 如中所述。

什么是单态限制? Haskell wiki所述的目标是:

Haskell类型推理中的反直觉规则。 如果您忘记提供类型签名,有时会填写此规则 使用类型默认规则创建具有特定类型的自由类型变量

这意味着,在某些情况下,如果您的类型不明确,即多态 编译器将选择将该类型实例化为不含糊的类型

我怎么修理它? 首先,您总是可以显式地提供一个类型签名,这将 避免触发限制:

plus :: Num a => a -> a -> a
plus = (+)    -- Okay!

-- Runs as:
Prelude> plus 1.0 1
2.0
或者,如果要定义函数,可以避免, 例如写:

plus x y = x + y
关掉它 可以简单地关闭限制,这样您就不必这样做 对代码进行任何修改都可以修复它。该行为由两个扩展控制: MonomorphismRestriction将启用它,这是 NomonomogomismRestriction将禁用它

您可以将以下行放在文件的最顶端:

{-# LANGUAGE NoMonomorphismRestriction #-}
如果使用的是GHCi,则可以使用:set命令启用扩展:

您还可以从命令行告诉ghc启用扩展:

ghc ... -XNoMonomorphismRestriction
注意:与通过命令行选项选择扩展相比,您应该更喜欢第一个选项

有关此扩展和其他扩展的说明,请参阅

完整的解释 我将尝试在下面总结您需要知道的一切,以了解 单态限制是,为什么它被引入和它的行为

一个例子 以下面的简单定义为例:

plus = (+)
plus = (+)
你会认为能够用加号替换+的每一次出现。特别是因为+::Num a=>a->a->a,您希望也有plus::Num a=>a->a->a

不幸的是,情况并非如此。例如,如果我们在GHCi中尝试以下操作:

Prelude> let plus = (+)
Prelude> plus 1.0 1
我们得到以下输出:

:4:6: 没有由文字“1.0”生成的分数整数实例 在里面 “plus”的第一个参数,即“1.0” 在表达式中:加1.0 1 在“it”的方程式中:it=加1.0 1 您可能需要:在较新的GHCi版本中设置-XMonomorphismRestriction

事实上,我们可以看到,加号的类型并不是我们所期望的:

Prelude> :t plus
plus :: Integer -> Integer -> Integer
发生的事情是编译器看到plus的类型Num a=>a->a->a,一种多态类型。 此外,上面的定义恰好符合我稍后将解释的规则,因此 他决定通过默认类型变量a使类型为单态。 我们可以看到,默认值是整数

请注意,如果您尝试使用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
plus = (+)

x :: Integer
x = plus 1 2

y :: Double
y = plus 1.0 2
我们希望所有这些函数的行为方式和类型都相同, i、 显示的类型:show a=>a->String

然而,在编译上述定义时,我们得到以下错误:

测试。hs:3:12: 没有因使用“Show”而导致的Show a1实例 类型变量“a1”不明确 相关绑定包括 x:a1在布拉赫绑定。hs:3:7 f2::a1->字符串绑定在blah处。hs:3:1 注意:有几个潜在的例子: 实例显示在“GHC.Float”中定义的双精度 实例显示浮点-在“GHC.Float”中定义 实例积分a,Show a=>Show GHC.Real.Ratio a -在“GHC.Real”中定义 …加上其他24个 在表达式中:show x 在表达式中:\x->show x 在“f2”的方程式中:f2=\x->show x 测试。hs:8:6: 没有因使用“Show”而导致的Show a0实例 类型变量“a0”不明确 相关绑定包括f4::a0->blah处绑定的字符串。hs:8:1 注意:有几个潜在的例子: 实例显示在“GHC.Float”中定义的双精度 实例显示浮点-在“GHC.Float”中定义 实例积分a,Show a=>Show GHC.Real.Ratio a -在“GHC.Real”中定义 …加上其他24个 在表达式中:show 在“f4”的方程式中:f4=show 所以f2和f4不编译。此外,在尝试定义这些函数时 在GHCi中没有错误,但是f2和f4的类型是->字符串

单态限制是f2和f4需要单态限制的原因 ghc和ghci的不同行为是由于不同的 默认规则

什么时候发生? 在Haskell中,如的定义,有两种不同的类型。 函数绑定和模式绑定。函数绑定就是 函数的定义:

f x = x + 1
f xs = (len, len)
  where
    len = genericLength xs
请注意,它们的语法是:

arg1 arg2。。。argn=expr 模保护和where声明。但它们其实并不重要

其中必须至少有一个参数

模式绑定是以下形式的声明:

<pattern> = expr
是一种模式绑定。它将模式加上一个变量绑定到表达式+

当模式绑定只包含一个变量名时,它被称为 简单的模式绑定

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

那么在形式上我们应该说,

声明组是相互依赖的绑定的最小集合

本规范第4.5.1节

然后是第4.5.5节:

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

组中的每个变量都由一个函数绑定,例如fx=x 或简单的模式绑定,例如plus=+;第4.4.3.2节,以及

组中的每个变量都有一个显式的类型签名 是由简单的模式绑定绑定的。e、 g.plus::Num a=>a->a->a;加号=+

我补充的例子

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

单态限制影响受限声明群

大多数情况下,您不会定义相互递归函数,因此也不会定义声明 组变成了一个绑定

它有什么作用? 第节中的两条规则描述了单态限制 第4.5.5条

第一条规则 通常Hindley-Milner对多态性的限制是唯一的类型 环境中不自由出现的变量可能是广义的。 此外,受约束声明的受约束类型变量 不能在该组的泛化步骤中泛化该组。 回想一下,如果类型变量必须属于某个 类型类;见第4.5.2节

突出显示的 第二部分介绍了单态限制。 它表示如果类型是多态的,即它包含一些类型变量 并且该类型变量受约束,即它有一个类约束: e、 类型Num a=>a->a->a是多态的,因为它包含和 因为a上有约束Num,所以也有约束。 那么它就不能被推广

简单地说,不泛化意味着函数plus的使用可能会改变它的类型

如果您有以下定义:

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
plus = (+)

x :: Integer
x = plus 1 2

y :: Double
y = plus 1.0 2
然后你会得到一个类型错误。因为当编译器看到加号时 在x的声明中调用一个整数,它将统一类型 变量a带有整数,因此加号的类型变为:

但是,当它检查y的定义时,它会看到加号 应用于双参数,但类型不匹配

请注意,您仍然可以使用plus而不会出现错误:

plus = (+)
x = plus 1.0 2
在这种情况下,加号的类型首先被推断为Num a=>a->a->a 但是在x的定义中,1.0需要一个分数 约束,将其更改为分数a=>a->a->a

根本原因 报告说:

需要规则1有两个原因,这两个原因都相当微妙

规则1防止计算意外地重复。 例如,genericLength是library Data.List中的标准函数 其类型由

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

现在考虑下面的表达式:

  let len = genericLength xs
  in (len, len)
let x = read "<something>" in show x
看起来似乎len应该只计算一次,但如果没有规则1,它将被忽略 可能会计算两次,在两个不同的重载中各计算一次。 如果程序员确实希望重复计算, 可以添加显式类型签名:

  let len :: Num a => a
      len = genericLength xs
  in (len, len)
在这一点上,我认为,来自《基本法》的例子更为清晰。 考虑函数:

f x = x + 1
f xs = (len, len)
  where
    len = genericLength xs
如果len是多态的,f的类型将是:

f :: Num a, Num b => [c] -> (a, b)
plus :: Fractional a => a -> a -> a
minus :: Num a => a -> a -> a
所以元组len,len的两个元素实际上是 不同的价值观!但这意味着计算是由genericLength完成的 必须重复此步骤才能获得两个不同的值

这里的基本原理是:代码包含一个函数调用,但没有引入 这个规则可能会产生两个隐藏的函数调用,这是违反直觉的

在单态限制下,f的类型变为:

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

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

 [(n,s)] = reads t
回想一下,reads是一个标准函数,其类型由签名给出

 reads :: (Read a) => String -> [(a,String)]
如果没有规则1,n将被分配类型∀ A.读一本⇒ a和s 类型∀ A.读一本⇒ 一串 后者是无效的类型,因为它本质上是不明确的。 无法确定在什么重载下使用s, 这也不能通过为s添加类型签名来解决。 因此,当使用第4.4.3.2节中的非简单模式绑定时, 推断的类型在其受约束的类型变量中始终是单态的, 无论是否提供类型签名。 在这种情况下,n和s在a中都是单态的

我相信这个例子是不言自明的。有时情况并非如此 应用规则会导致类型歧义

如果按照上面的建议禁用扩展,则在 正在尝试编译上述声明。然而,这并不是一个真正的问题: 您已经知道,当使用read时,您必须以某种方式告诉编译器 它应该尝试分析哪种类型

第二条规则 任何单态类型变量,在对 整个模块已完成,被视为不明确,并已解决 使用第4.3.4节的默认规则添加到特定类型。 这意味着。如果您有您通常的定义:

plus = (+)
plus = (+)
这将有一个类型Num a=>a->a->a,其中a是a 由于上述规则1,单态类型变量。一旦整个模块 编译器将只选择一个类型来替换 根据违约规则

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

请注意,这是在推断整个模块之后完成的

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

plus = (+)

x = plus 1.0 2.0
在模块内部,在类型默认值之前,加号的类型为: 分数a=>a->a->a有关发生这种情况的原因,请参见规则1。 此时,按照默认规则,a将替换为Double 我们将有plus::Double->Double->Double和x::Double

违约 如前所述,存在一些默认规则,如中所述, 推论者可以采用,并将多态类型替换为单态类型。 每当类型不明确时,就会发生这种情况

例如,在表达式中:

  let len = genericLength xs
  in (len, len)
let x = read "<something>" in show x
因此x的类型为reada=>a。但许多类型都满足了这一约束条件: Int、Double或例如。选择哪一个?没有什么可以告诉我们的

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

let x = read "<something>" :: Int in show x
结果应该是什么

如前所述,1的类型Num a=>a,可以使用多种类型的数字。 选择哪一个

几乎每次使用数字时都会出现编译器错误不是一件好事, 因此引入了违约规则。规则是可以控制的 使用默认声明。通过指定默认值T1、T2、T3,我们可以更改 推断器如何默认不同的类型

如果出现以下情况,则不明确类型变量v为默认值:

v只出现在C类的约束中,C是一个类 i、 e.如果它出现在:Monad m v中,则它不可默认。 这些类中至少有一个是Num或Num的子类。 所有这些类都在Prelude或标准库中定义。 默认类型变量将替换为默认列表中的第一个类型 这是所有不明确变量类的实例

默认声明为默认整数,双精度

例如:

plus = (+)
minus = (-)

x = plus 1.0 1
y = minus 2 1
推断出的类型为:

f :: Num a, Num b => [c] -> (a, b)
plus :: Fractional a => a -> a -> a
minus :: Num a => a -> a -> a
通过默认规则,将成为:

plus :: Double -> Double -> Double
minus :: Integer -> Integer -> Integer
请注意,这解释了为什么在问题的示例中只有排序 定义引发了一个错误。不能默认类型Ord a=>[a]->[a] 因为Ord不是一个数值类

延期违约 请注意,GHCi附带或, 也可以使用ExtendedDefaultRules扩展名在文件中启用

默认类型变量不仅需要出现在所有 这些类是标准的,其中必须至少有一个类 Eq、Ord、Show或Num及其子类

此外,默认声明是default、Integer、Double

这可能会产生奇怪的结果。以问题为例:

前奏曲>:集合-Xmonomogromism限制 序言>导入数据.ListsortBy 序曲数据.List>let sort=sortBy compare 序曲数据列表>:t排序 排序::[]->[] 在ghci中,我们没有得到类型错误,但Ord a约束会导致 违约是毫无用处的

有用的链接 关于单态限制有很多资料和讨论

以下是一些我认为有用的链接,可以帮助您理解或深入了解该主题:

Haskell的wiki页面: 这个 一个容易接近的好地方 第6.2节和第6.3节涉及单态限制和类型默认
为什么突然发布公共服务公告?另外,我认为在你的回答中,关掉它可能应该是更重要的建议?元问题是4个月前提出的。我在两周前发布了下面答案的草稿。不久前,我在聊天中也提到了这两个事实。对我来说,这并不突然。明天我将看看我能做些什么来突出最重要的信息。啊,我错过了元参考。我还没有读完这个答案,因为我注意到有几行GHCi在这个答案中被称为错误。当我尝试它们时,不要出错,所以我认为它可能有点过时。例如,在GHCi中,让plus=+后跟plus 1.0 1即可,打印2.0。这是因为在这5年左右的时间里发生了一些变化吗?@Enlico我相信在GHCi 7.8.1中,NoMonomorphismRestriction扩展默认是打开的,请参见:但是对于编译代码来说,这不是真的。事实上,我的回答提到了这一点:您可能需要:在较新的GHCi版本中设置-XMonomorphismRestriction。好的,谢谢。很抱歉,我没有看到注释和我观察到的东西之间的联系。