【问题标题】:Why is a built-in function applied to too few arguments considered to be in weak head normal form?为什么将内置函数应用于被认为是弱头范式的参数太少?
【发布时间】:2014-08-18 07:09:20
【问题描述】:

Haskell definition 说:

一个表达式是弱头范式(WHNF),如果它是:

  • 构造函数(最终应用于参数),例如 True、Just(正方形 42)或 (:) 1
  • 一个内置函数应用于太少的参数(可能没有),例如 (+) 2 或 sqrt。
  • 或 lambda 抽象 \x -> 表达式。

为什么内置函数会受到特殊对待?根据 lambda 演算,部分应用函数与任何其他函数之间没有区别,因为最后我们只有一个参数函数。

【问题讨论】:

  • 我冒昧地编辑了 Haskell wiki 页面,并解释了为什么内置函数和 lambdas 之间的这种区别与语义无关。 (还有一个关于严格构造函数字段的微妙之处的注释。)
  • 顺便说一句,该 wiki 页面实际上并不是 Haskell's official definition 的一部分。然而官方报告甚至没有提到这个问题,似乎更愿意假设读者知道指称语义......

标签: haskell lambda-calculus reduction partial-application weak-head-normal-form


【解决方案1】:

应用于参数的普通函数,如下所示:

(\x y -> x + 1 : y) 1

可以归约,给:

\y -> 1 + 1 : y

在第一个表达式中,“最外层”的东西是一个应用程序,所以它不在 WHNF 中。其次,最外层是一个 lambda 抽象,所以它在 WHNF 中(尽管我们可以在函数体内部做更多的归约)。

现在让我们考虑一个内置(原始)函数的应用:

(+) 1

因为这是一个内置函数,所以没有函数体可以用1 代替第一个参数。评估者“只知道”如何评估(+) 的完全“饱和”应用程序,例如(+) 1 2。但是对于 partially 应用的内建,没有什么可以做的;我们所能做的就是生成一个数据结构,描述“将 (+) 应用于 1 并等待另一个参数”,但这正是我们试图减少的东西。所以我们什么都不做。

内置函数很特殊,因为它们不是由 lambda 演算表达式定义的,因此归约过程无法“看到内部”它们的定义。因此,与普通函数应用程序不同,内置函数应用程序必须通过累积参数来“减少”,直到它们完全“饱和”(在这种情况下,减少到 WHNF 是通过运行内置函数的任何神奇实现) .无法进一步减少不饱和的内置应用程序,因此在 WHNF 中已经存在。

【讨论】:

  • 运行时系统是否实际上减少了部分应用的用户定义函数?当然,我们希望减少 (\x->if x==1 then function1 else function2) k,但在我看来,实际上减少 (\x y -> x + x*y + x^y) k 需要一个特殊的表示。
  • @dfeuer 由于运行时的 GHC 不会将函数表示为 lambda 项,这确实很难。 GHC 通常只在函数具有所有参数时才应用它。它有一个 special representation 的部分应用函数。
  • @dfeuer 虽然你当然可以编写一个运行时系统来减少像这样的部分应用函数。我通过简单的图形缩减实现了一种玩具惰性函数语言。这更容易,因为您不必知道或关心函数何时具有所有参数;部分应用程序与最终应用程序相同(内置程序除外)。诀窍是您不使用文本术语,而是使用图表。因此,尽管我们会将您的示例简化为\y -> k + k*y + k^y,但在实际系统中,每个 k 实际上是对单个节点的引用。
  • Ben,我真正的意思是,any 类型的部分应用函数的简化(即,除了用一个值替换参数之外什么都不做)最多可以,做少量而恒定的工作,永远不会产生底部。因此,我个人认为不将此类应用程序纳入 WHNF 的唯一原因是简化对 WHNF 含义的描述。
  • @dfeuer 哦,对了,当它不是 JIT 编译的时候。不过还是有可能的。我认为 GHC 实际上会在您使用 FFI 导出闭包时生成少量运行时代码(因为没有它就无法动态构建它们的 C 函数指针),但没有类似的。
【解决方案2】:

考虑

前奏曲> 让 f n = [(+x) | x Prelude> let g = f 20000000 :: Int -> Int

g 此时不在 WHNF 中!您可以通过评估 g 3 来看到这一点,这需要明显的滞后,因为您需要 WHNF 才能应用参数。那是遍历列表以搜索正确的内置函数的时候。但之后,这个选择是固定的,g 是 WHNF(实际上是 NF:对于 lambdas 也是一样,也许你的问题是什么意思),因此任何后续调用都会立即给出结果。

【讨论】:

  • 为什么在第一次运行g 3 之后没有记住f 20000000?我的意思是为什么对g 3 的两个后续调用需要相同的显着时间?
  • 好吧,他们没有。它记忆的,至少在我安装的所有ghcis 中(7.4、7.6、7.8)。由于MonomorphismRestriction 默认关闭,这需要显式的单态签名,也许你忘记了? (多态函数有一个隐藏的额外参数。)
  • 自 GHC 7.8.1 起,GHCi 中默认关闭了单态限制。 有什么办法可以开启吗?多态函数的额外参数是什么意思?
  • @RobertZaremba:您可以通过设置-XMonomorphismRestriction 标志来打开它...但是不要,这是邪恶的;您始终可以使用显式签名来实现相同的目标(例如我的示例中的Int -> Int);这更加透明。 — Haskell 多态是参数多态,这意味着如果一个函数的签名中有一些类型变量(这里我们有f :: (Num a, Enum a) => Int -> a->a)那么这个类型的属性(即它如何可以用作一个数字) 需要作为“字典参数”传递给函数。这就是为什么记忆并不总是按预期工作的原因。
猜你喜欢
  • 1970-01-01
  • 2011-10-15
  • 2015-01-01
  • 2016-04-22
  • 2020-06-09
  • 2011-04-30
  • 2020-06-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多