29

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

考虑以下示例:

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

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

链接摘要:

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

  1. GHC 类型默认:Haskell 报告第 4.3.4 节
  2. GHCi 的扩展类型默认:使用 GHCi 第 2.4.5 节
  3. 单态限制:Haskell wiki
4

3 回答 3

32

这里还有另一个因素,在 acfoltzer 包含的一些链接中提到,但在这里可能值得明确说明。您遇到了单态限制的影响。当你说

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

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

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

在尝试学习类型系统如何工作时,将类型推断的各个方面分开是非常有用的

  1. 'follow the plan',将多态定义专门用于特定用例:一个相当强大的约束解决问题,需要通过回链实现基本统一和实例解析;和

  2. '猜测计划',泛化类型以将多态类型方案分配给没有类型签名的定义:这非常脆弱,而且你越是越过基本的 Hindley-Milner 学科,具有类型类,具有更高级别的多态性,具有GADTs,奇怪的事情变成了。

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

于 2011-08-14T09:00:26.220 回答
6

发生这种情况是因为GHCi 中的类型默认设置,如此此处此处此处等所讨论的。不幸的是,这似乎很难搜索,因为在您知道“类型默认”这个短语之前,有很多方法可以描述这种行为。

更新:D'oh。删除了糟糕的例子。

于 2011-08-14T05:42:35.613 回答
4

由于没有其他人提到为什么存在单态限制,我想我会从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 程序员都会产生意外或晦涩的错误消息。关于替代品的讨论很多。Glasgow Haskell 编译器(GHC,第 9.1 节)提供了一个标志:

-fno-monomorphism-restriction

完全压制限制。但在这段时间里,并没有真正令人满意的替代方案出现。

我发现论文对单态限制的语气非常有趣。

于 2012-10-19T17:37:34.833 回答