7

我一直在阅读这个http://www.haskell.org/haskellwiki/Hask。我在这部分苦苦挣扎..

undef1 = undefined :: a -> b
undef2 =  \_ -> undefined

为什么他们会有这样的行为..

seq undef1 () = undefined
seq undef2 () = ()
undef2 () = undefined

这是什么原因?我想了解这种行为,但我什至不知道从哪里开始。特别是,为什么 undef2 在严格评估下表现不同?

4

4 回答 4

9

特殊函数seq将其第一个参数评估为弱头范式,然后返回其第二个参数。可以在此处找到对 WHNF 的一个很好的解释,但出于此答案的目的,我将仅使用Haskell wiki 定义

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

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

重要的一点是,当表达式是构造函数时,seq只查看构造函数的标记。因此,seq (Just undefined) 1评估为1

另一个重要的一点是 Haskell 中的所有类型都被提升了- 也就是说,评估类型的值可能导致执行无限循环或抛出异常(通常使用erroror undefined)。在我们评估之后seq a b,我们可以确定评估aWHNF 不会导致无限循环或异常。

有了这些知识,让我们看一下您的示例:

undef1 = undefined :: a -> b
undef2 =  \_ -> undefined

seq undef1 ()被评估时,seq首先尝试找出上述三个类别中的哪一个undef1。但是undef1undefined,因此整个表达式的计算结果为undefined

但是,在 的情况下seq undef2 () = (),第一个参数是 lambda 抽象。由于seq看不到 lambda,它返回第二个参数。

第三个示例undef2 () = undefined只是评估应用程序的直接结果(\_ -> undefined) ()

于 2013-04-16T16:00:51.200 回答
3

他们不是一回事。undef1 是从 a 到 b 的函数,但未定义哪个函数。将 undef1 评估为头部正常形式会给您 undefined。

undef2 是从 a 到 b 的函数。具体来说,它是一个忽略其参数并返回未定义的函数。但是 undef2 本身并不是未定义的。只有当您尝试评估函数时(如在第三行中),您才会得到未定义。因此,当您将 undef2 评估为头范式时,您会得到一个正确的函数,而不是未定义的。

用更重要的术语来说(总是不准确的根源,但如果你更熟悉它,它很好地说明了这一点),将 undef1 视为永远不会返回的属性获取器。(在 Haskell 中,不返回和未定义在语义上是等价的。) undef2 是一个返回函数的属性 getter;如果您调用它,该函数将不会返回。在 C# 中:

Func<Object, Object> Undef1 {
  get {
    while (true) {}
    return null;
  }
}

Func<Object, Object> Undef2 {
  get {
    return _ -> {
      while (true) {}
      return null;
    }
  }
}

现在你的测试变成:

var x = Undef1;
var y = Undef2;
var z = Undef2(null);
于 2013-04-16T15:48:34.057 回答
3

为了这个问题,假设有三件事可以强制一个值被实际评估:

  • 该值的模式匹配
  • 将值应用于参数
  • 使用它作为第一个参数seq

实际情况稍微复杂一些,但在这里并不重要。

此外,这种强制只发生在某些外部表达式的上下文中,因此与其以某种抽象的方式将这些视为“强制评估”,不如将它们视为使外部表达式的评估依赖于对外部表达式的评估。价值。这就是为什么,例如,在任何意义上seq x x都不会强制x,因为无论如何这都是表达式的最终值;它说当评估外部表达式(其值为x)时,它也应该评估x,这是多余的。

最后,任何依赖于强制未定义值的值本身就是未定义的。


遍历每个表达式:

seq undef1 () = undefined

在这种情况下undef1是未定义的,并且seq undef1 x是一个表达式,其值是x并且取决于评估undef1。因此,无论第二个参数seq是什么,整个表达式都是未定义的。

seq undef2 () = ()

在这种情况下,undef2不是未定义的,而是应用它的结果。seq undef2 x是一个表达式,其值为x并取决于评估undef2。这不会导致任何问题,并且第一个参数 toseq被丢弃,所以表达式的值在()这里。

undef2 () = undefined

在这种情况下,我们undef2直接申请。表达式undef2 ()取决于undef2(这很好) 的评估,并评估应用的结果undef2,在这种情况下是undefined.

将此与第四种情况进行对比:

undef1 () = undefined

在这种情况下,我们正在应用undef1,因此表达式的值取决于评估undef1,这是未定义的,因此整个表达式也是如此。这与之前使用 的表达式具有相同的“值” undef2,但原因却截然不同!

于 2013-04-16T15:53:28.577 回答
3

好吧,只是写一个比其他人更简短的答案:为了强制评估类似 的表达式f x,Haskell 首先需要弄清楚函数f是什么,然后应用它x。用更专业的术语来说,无论何时f x强制,都f必须被强制。

现在让我们回到您的定义:

undef1 :: a -> b
undef1 = undefined

undef2 :: a -> b
undef2 =  \_ -> undefined

强迫undef1undef2产生不同的结果。强制undef1undefined立即产生,而强制undef2会产生一个 thunk,当应用于某些参数时,会产生undefined.

我怀疑你误解了什么seq;我得到的印象是您认为seq undef2 ()应该适用undef2(),但事实并非如此。所做的是seq :: a -> b -> b强制其第一个参数并返回第二个参数。seq undef1 ()和之间的区别seq undef2 ()很简单,只有第二个成功强制其第一个参数,然后它只是简单地返回()

如果我对上面代码的意图是正确的,那么您正在寻找的是($!) :: (a -> b) -> a -> b,它将其第一个参数应用于第二个参数并强制结果。所以这两个都失败了undefined

undef1 $! ()
undef2 $! ()
于 2013-04-16T16:26:02.143 回答