【问题标题】:What does the exclamation mark mean in a Haskell declaration?Haskell 声明中的感叹号是什么意思?
【发布时间】:2010-11-02 20:21:19
【问题描述】:

当我尝试使用一个真实的项目来驱动它来学习 Haskell 时,我遇到了以下定义。我不明白每个论点前面的感叹号是什么意思,而且我的书似乎没有提到它。

data MidiMessage = MidiMessage !Int !MidiMessage

【问题讨论】:

  • 我怀疑这可能是一个非常常见的问题;我清楚地记得自己也在想同样的事情,早在很久以前。

标签: haskell syntax lazy-evaluation


【解决方案1】:

这是一个严格的声明。基本上,这意味着在创建数据结构值时必须将其评估为所谓的“弱头范式”。让我们看一个例子,这样我们就能明白这意味着什么:

data Foo = Foo Int Int !Int !(Maybe Int)

f = Foo (2+2) (3+3) (4+4) (Just (5+5))

上面的函数f,在计算时会返回一个“thunk”:即要执行的代码来计算它的值。那时,Foo 甚至还不存在,只是代码。

但在某些时候,有人可能会尝试查看它的内部,可能是通过模式匹配:

case f of
     Foo 0 _ _ _ -> "first arg is zero"
     _           -> "first arge is something else"

这将执行足够多的代码来做它需要做的事情,仅此而已。所以它将创建一个带有四个参数的 Foo(因为如果它不存在,你就无法查看它的内部)。第一个,因为我们正在测试它,我们需要一直评估到4,我们意识到它不匹配。

第二个不需要评估,因为我们没有测试它。因此,我们不会将6 存储在该内存位置,而是存储代码以供以后可能的评估,(3+3)。只有有人看它才会变成 6。

然而,第三个参数前面有一个!,因此被严格评估:(4+4) 被执行,8 存储在那个内存位置。

第四个参数也被严格评估。但这里有点棘手:我们没有完全评估,而只是评估弱的正常头部形状。这意味着我们确定它是Nothing 还是Just 某物,然后存储它,但我们不再继续。这意味着我们存储的不是Just 10,而是实际Just (5+5),内部的thunk 没有被评估。了解这一点很重要,尽管我认为这一切的含义都超出了这个问题的范围。

如果启用BangPatterns 语言扩展,您可以以相同的方式注释函数参数:

f x !y = x*y

f (1+1) (2+2) 将返回 thunk (1+1)*4

【讨论】:

  • 这很有帮助。然而,我不禁想知道 Haskell 术语是否让人们用“弱正常头部形式”、“严格”等术语来理解事情变得更加复杂。如果我理解正确,听起来像!运算符只是意味着存储表达式的评估值,而不是存储匿名块以供以后评估。这是一个合理的解释还是有更多的原因?
  • @David 问题是评估价值的程度。弱头范式意味着:评估它直到到达最外层的构造函数。正常形式意味着评估整个值,直到没有未评估的组件留下。因为 Haskell 允许各种级别的评估深度,所以它有丰富的术语来描述这一点。您不会在仅支持按值调用语义的语言中发现这些区别。
  • @David:我在这里写了更深入的解释:Haskell: What is Weak Head Normal Form?。虽然我没有提到 bang 模式或严格性注释,但它们等同于使用 seq
  • 只是为了确保我理解:这种懒惰仅与编译时参数的未知有关吗? IE。如果源代码真的有语句(2+2),是否还会优化到4,而不是有代码加已知数?
  • Hi-Angel,这实际上取决于编译器想要优化的程度。虽然我们用刘海来表示我们想强迫某些东西变成弱正常的头部形状,但我们没有(也不需要或想要)一种方式来说“请确保你还没有评估这个 thunk one更进一步。”从语义的角度来看,给定一个 thunk “n = 2 + 2”,您甚至无法判断对 n 的任何特定引用是现在正在评估还是之前已经评估。
【解决方案2】:

查看严格和非严格构造函数参数之间区别的一个简单方法是它们在未定义时的行为方式。给定

data Foo = Foo Int !Int

first (Foo x _) = x
second (Foo _ y) = y

由于second 不评估非严格参数,因此传入undefined 不会导致问题:

> second (Foo undefined 1)
1

但是严格的参数不能是undefined,即使我们不使用值:

> first (Foo 1 undefined)
*** Exception: Prelude.undefined

【讨论】:

  • 这比 Curt Sampson 的回答要好,因为它实际上解释了 ! 符号具有的用户可观察效果,​​而不是深入研究内部实现细节。
【解决方案3】:

我相信这是一个严格的注释。

Haskell 是一种纯粹的 惰性 函数式语言,但有时惰性的开销可能过多或浪费。因此,为了解决这个问题,您可以要求编译器全面评估函数的参数,而不是解析 thunk。

此页面上有更多信息:Performance/Strictness

【讨论】:

  • 嗯,您引用的那个页面上的示例似乎在谈论 !调用函数时。但这似乎与将其放在类型声明中不同。我错过了什么?
  • 创建一个类型的实例也是一个表达式。给定提供的参数,您可以将类型构造函数视为返回指定类型的新实例的函数。
  • 事实上,就所有意图和目的而言,类型构造函数函数;您可以部分应用它们,将它们传递给其他函数(例如,map Just [1,2,3] 得到 [Just 1, Just 2, Just 3])等等。我发现考虑与它们进行模式匹配的能力以及完全不相关的工具很有帮助。
猜你喜欢
  • 2011-01-11
  • 1970-01-01
  • 2021-09-18
  • 1970-01-01
  • 1970-01-01
  • 2014-07-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多