【问题标题】:Evaluating \_ -> undefined in Haskell在 Haskell 中评估 \_ -> undefined
【发布时间】:2013-04-09 02:26:51
【问题描述】:

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

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

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

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

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

【问题讨论】:

    标签: haskell


    【解决方案1】:

    特殊函数seq 将其第一个参数评估为弱头范式,然后返回其第二个参数。可以在 here 找到对 WHNF 的一个很好的解释,但为了回答这个问题,我将只使用 Haskell wiki definition

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

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

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

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

    有了这些知识,让我们看看你的例子:

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

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

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

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

    【讨论】:

      【解决方案2】:

      它们不是一回事。 undef1 是从 a 到 b 的函数,但未定义哪个函数。将 undef1 计算为 head 范式会得到 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);
      

      【讨论】:

        【解决方案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。这样没有问题,seq 的第一个参数被丢弃了,所以这里的表达式的值为()

        undef2 () = undefined
        

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

        对比第四种情况:

        undef1 () = undefined
        

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

        【讨论】:

          【解决方案4】:

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

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

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

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

          我怀疑你误解了seq 的作用;我的印象是您认为seq undef2 () 应该将undef2 应用于(),但它并没有这样做。 seq :: a -&gt; b -&gt; b 所做的是强制其第一个参数并返回第二个参数。 seq undef1 ()seq undef2 () 之间的区别仅仅是,只有第二个成功地强制其第一个参数,但随后它只是返回 ()

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

          undef1 $! ()
          undef2 $! ()
          

          【讨论】:

          • 呵呵,是的。我必须承认。我对seq并不完全清楚。谢谢。