【问题标题】:Why do 3 and x (which was assigned 3) have different inferred types in Haskell? [duplicate]为什么 3 和 x(分配为 3)在 Haskell 中有不同的推断类型? [复制]
【发布时间】:2011-10-26 16:14:12
【问题描述】:

Haskell 中的类型推断有一点学习曲线(至少可以这么说!)。开始学习它的一个好方法是使用简单的示例。所以,下面是一个类型推断的“hello world”。

考虑以下示例:

Prelude> :t 3
3 :: (Num t) => t
Prelude> let x = 3
Prelude> :t x
x :: Integer

因此问题是:为什么 3 和 x 有不同的类型?

链接摘要:

阅读下面的答案以获得完整的故事;这里只是一个链接摘要:

  1. GHC 类型默认:Haskell Report section 4.3.4
  2. GHCi 的扩展类型默认:Using GHCi section 2.4.5
  3. 单态限制:Haskell wiki

【问题讨论】:

  • 是的,新标题比旧标题更具体/更清晰!
  • 我删除了原始问题的底部;没有增加足够的价值来保证被纳入。

标签: haskell type-inference monomorphism-restriction


【解决方案1】:

由于没有其他人提到为什么存在单态限制,我想我会从A History of Haskell: Being Lazy With Class 添加这一点。

6.2 单态限制 早期争议的主要来源是所谓的“单态限制”。认为 genericLength 有这个重载类型:

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

现在考虑这个定义:

f xs = (len, len) 
     where len = genericLength xs 

看起来len 应该只计算一次,但是 它实际上可以计算两次。为什么?因为我们可以推断出类型 len :: (Num a) => a;当用字典传递去糖时 翻译,len 成为一个函数,每次调用一次 len 的出现,每个都可能用于不同的类型。

Hughes 强烈认为,默默地复制是不可接受的 以这种方式计算。他的论点是由他的一个程序引起的 写的速度比他预期的要慢得多。 (这是 诚然,用一个非常简单的编译器,但我们不愿意做 如此大的性能差异取决于编译器 优化。)

经过多次辩论,委员会通过了 现在臭名昭著的单态性限制。简而言之,它说 看起来不像函数的定义(即没有参数 左侧)在任何重载类型中都应该是单态的 变量。在此示例中,规则强制 len 同时使用 在两次出现时键入,这解决了性能问题。 程序员可以为len 提供显式类型签名,如果 需要多态行为。

单态限制是 显然是语言上的一个疣。它似乎会咬住每一个新的 Haskell 程序员通过引起意外或晦涩的错误消息。 关于替代品的讨论很多。格拉斯哥 Haskell 编译器 (GHC,第 9.1 节)提供了一个标志:

-fno-monomorphism-restriction

完全取消限制。但在这段时间里,没有真正 已经发展出令人满意的替代方案。

我发现论文中关于单态限制的语气很有趣。

【讨论】:

    【解决方案2】:

    这是因为 type defaulting in GHCi,如 herehereherehere 等所讨论的那样。不幸的是,这似乎很难搜索,因为在您知道“类型默认”这个短语之前,有很多方法可以描述这种行为。

    更新:哦。删除了不好的例子。

    【讨论】:

    • Ehrm,这个答案很有误导性。 x 拥有Num a => a 类型会非常好。事实上,如果你禁用单态限制或明确给出x 这个类型,它就会有那个类型。在这种情况下,做x * 3.0 并没有什么虚假的。数字类型的默认值也不是特定于 ghci 的。
    • @sepp2k:哎呀,即使我尝试回答问题,单态限制也会出现!你完全正确。
    • @sepp2k:感谢类型推断和类型默认之间的联系!我同意,寻找前者不一定能找到后者。
    【解决方案3】:

    这里还有另一个因素,在 acfoltzer 包含的一些链接中提到,但在这里可能值得明确说明。您遇到了monomorphism restriction 的效果。当你说

    let x = 5
    

    你为一个变量做了一个顶级定义。 MR 坚持认为,当这些定义没有类型签名时,应该通过为未解析的类型变量选择(希望)合适的默认实例,将其专门化为单态值。相反,当您使用:t 请求推断类型时,不会施加此类限制或默认设置。所以

    > :t 3
    3 :: (Num t) => t
    

    因为3 确实被重载了:它被任何数字类型所接受。默认规则选择Integer作为默认数字类型,所以

    > let x = 3
    > :t x
    x :: Integer
    

    但是现在让我们关闭 MR。

    > :set -XNoMonomorphismRestriction
    > let y = 3
    > :t y
    y :: (Num t) => t
    

    没有 MR,定义就像它可以是多态的一样,就像 3 一样重载。只是检查...

    > :t y * (2.5 :: Float)
    y * (2.5 :: Float) :: Float
    > :t y * (3 :: Int)
    y * (3 :: Int) :: Int
    

    请注意,根据相关Num 实例提供的fromInteger 方法,多态y = 3 在这些用途中的专门化程度不同。也就是说,y3 的特定表示无关,而是与3 的表示的构造方案相关联。天真地编译,这是慢的秘诀,有些人认为这是 MR 的动机。

    在关于单态性限制是更小还是更大的邪恶的辩论中,我(在当地假装是)中立。我总是为顶级定义编写类型签名,因此我想要实现的目标没有歧义,而 MR 则无关紧要。

    在尝试了解类型系统的工作原理时,将类型推断的各个方面分开是非常有用的

    1. “遵循计划”,将多态定义专门用于特定用例:一个相当稳健的约束解决问题,需要通过回链实现基本统一和实例解析;和

    2. '猜测计划',泛化类型以将多态类型方案分配给没有类型签名的定义:这是非常脆弱的,而且你越是越过基本的 Hindley-Milner 规则,使用类型类,使用更高等级的多态性,使用 GADT,奇怪的东西就变成了。

    了解第一个是如何工作的,并理解为什么第二个是困难的,这很好。类型推断中的许多奇怪之处都与第二个有关,并且与单态限制等启发式方法有关,试图在面对歧义时提供有用的默认行为。

    【讨论】:

    • 无论你对编译程序的 MR 有什么看法,每个人似乎都同意它不应该在 GHCi 提示符下默认生效,如本例所示。 GHC bug #3202 解决了这个问题。原计划在刚刚发布的 7.2.1 中,但看起来它没有进入。希望它会在下一个版本的 GHC 中修复。
    • @Yitz 这确实是一个不错的结果。我倾向于说默认情况下应该关闭 MR,但最好收集有关现有代码库中会引入多少类型/性能问题的硬数据。我还想知道 GHC 的专业化机制是否可以让我们双管齐下。但我推测。
    • “MR 坚持认为这样的定义,如果没有类型签名,应该专门用于单态值”我一直试图理解 MR 一段时间,但仍然不完全那里...如果 MR 强迫您指定签名,我可以理解,但简单地默认为某种类型对我来说似乎是最糟糕的选择。
    猜你喜欢
    • 2021-06-12
    • 1970-01-01
    • 1970-01-01
    • 2013-08-29
    • 1970-01-01
    • 2018-12-22
    • 1970-01-01
    • 2011-11-06
    • 1970-01-01
    相关资源
    最近更新 更多