【问题标题】:Is operator && strict in Haskell?Haskell 中的运算符 && 是否严格?
【发布时间】:2011-11-28 13:56:06
【问题描述】:

例如,我有一个操作 fnB :: a -> Bool,在 fnA :: Bool 返回 False 之前没有任何意义。在 C 语言中,我可以将这两个操作组合在一个 if 块中:

if( fnA && fnB(a) ){ doSomething; }

并且 C 将保证 fnBfnA 返回 false 之前不会执行。

但是 Haskell 是懒惰的,而且通常不能保证什么操作会先执行,直到我们不使用 seq$! 或其他东西来使我们的代码变得严格。一般来说,这就是我们需要快乐的东西。但是使用&& 运算符,我希望fnBfnA 返回其结果之前不会被评估。 Haskell 是否通过&& 提供这样的保证?即使 fnA 返回 False,Haskell 是否会评估 fnB

【问题讨论】:

    标签: haskell logical-operators strictness


    【解决方案1】:

    函数(&&) 只有当它的第一个参数是True 时,它的第二个参数才是严格的。它的第一个论点总是严格的。这种严格/懒惰是评估顺序的保证。

    所以它的行为与 C 完全一样。不同之处在于,在 Haskell 中,(&&) 是一个普通函数。在 C 中,这是不可能的。

    但是 Haskell 是懒惰的,而且通常不能保证哪个操作会先执行,除非我们不使用 seq、$! 或其他东西来使我们的代码严格。

    这是不正确的。真相更深。

    严格的速成课程:

    我们知道(&&) 的第一个参数是严格的,因为:

    ⊥ && x = ⊥
    

    这里,⊥ 类似于undefined 或无限循环(⊥ 发音为“底部”)。我们还知道(False &&) 在其第二个参数中是非严格的:

    False && ⊥ = False
    

    它不可能计算它的第二个参数,因为它的第二个参数是⊥,它不能被计算。但是,(True &&) 函数的第二个参数是严格的,因为:

    True && ⊥ = ⊥
    

    因此,我们说(&&) 在其第一个参数中始终是严格的,并且仅当第一个参数为True 时才在其第二个参数中是严格的。

    评估顺序:

    对于(&&),其严格属性足以保证执行顺序。情况并非总是如此。例如,(+) :: Int -> Int -> Int 在两个参数中始终是严格的,因此可以先评估任一参数。但是,您只能通过在 IO monad 中捕获异常或使用 unsafe 函数来区分。

    【讨论】:

    • 实际上,第二个参数从不严格。它返回它而不评估它。
    • @larsmans:实际上,任何返回值而不对其求值的函数在该参数中都是定义严格的。例如,函数id 在其参数中始终是严格的。
    • 这个答案似乎假设顺序评估。一个实现可以合法地并行评估这两个参数,只要第一个获得它的资源份额并且第二个的异常得到正确处理。这将是一个令人惊讶的实现,但也是合法的。
    • @dfeuer:好吧,如果我们要学究起来的话……首先,问题是专门询问评估顺序,所以我们必须讨论它才能回答问题。其次,如果我们谈论的是关于纯 Haskell 语义的求值顺序,我们可以说因为无法求值 ⊥,False && ⊥ 不能求值 ⊥,所以它必须先求值 False 否则它不会知道它不应该评估⊥。所以不可能并行评估False && ⊥。不过,您可以编写一个并行共享内存程序,它会给出相同的结果。
    • @FredFoo - 从技术上讲,Haskell 不允许“不评估就返回值”; && tail 调用它的第二个参数,而第一个参数是True
    【解决方案2】:

    正如其他人所指出的,(&&) 在其论点之一上自然是严格的。根据标准定义,它的第一个参数是严格的。您可以使用flip 翻转语义。

    作为附加说明:请注意,(&&) 的参数不会有副作用,因此您需要关心 x && yy 中是否严格的原因只有两个:

    • 性能:如果y 需要很长时间来计算。
    • 语义:如果您期望y 可以是底部。

    【讨论】:

    • 我喜欢你关于副作用的 cmets。如果你不想关心顺序,可以定义一个真正对称的和:a &=& b = (a && b) `unamb` (b && a)
    • (&&) 的参数如果使用 unsafePerformIO 可能会产生副作用,但你会得到你想要的!
    • @luqui 实际上,Data.Unamb.pand 函数的定义正是如此(模数抽象层)。
    【解决方案3】:

    Haskell 是惰性的,一般来说,不能保证先执行什么操作

    不完全是。 Haskell是的(unsafePerformIOIO的实现除外),没有办法观察哪个操作会先执行(unsafePerformIO除外以及IO的实现。执行顺序只是与结果无关

    && 有一个 9 值的真值表,包括一个或两个参数为 undefined 的情况,它准确地定义了操作:

    a           b           a && b
    True        True        True
    True        False       False
    True        undefined   undefined
    False       True        False
    False       False       False
    False       undefined   False
    undefined   True        undefined
    undefined   False       undefined
    undefined   undefined   undefined
    

    只要实现遵循该表,它就可以按照它想要的任何顺序执行。

    (如果您研究该表,您会注意到 sequential 实现无法遵循它,除非它首先执行 a,然后 b iff a 为 True . 但是 Haskell 实现不需要是顺序的!一个实现总是允许在任何时候执行b;你唯一的保证是,根据表格,执行的结果b 只能在 a 为 True 时影响您的程序。)

    (请注意,“懒惰”是编写具有上述真值表的 函数 的唯一方法;在 C 或 ML 等语言中,所有五行带有 @987654334 @ 在任一参数中都将强制以 undefined 作为结果,而在 Haskell 中(以及在 C 中,因为 && 是内置于 C 语言中的)其中一行可以以 False 作为结果。 )

    【讨论】:

      【解决方案4】:

      我相信它会按您期望的方式工作;如果 LHS 评估为 True,则评估 RHS。但是,假设 RHS 没有副作用,您怎么知道(或关心)?

      编辑:我猜 RHS 可能是 undefined,然后你会关心...

      【讨论】:

      • 它不是未定义的。 Haskell 报告提供了作为规范的 Prelude 的完整列表。关心这一点完全没问题,因为(&&) 可用于将简单快速的测试与需要大量计算的测试结合起来。
      • 我的意思是 RHS 可能是未定义的,然后你会关心它是否被评估
      猜你喜欢
      • 2012-06-26
      • 2023-02-06
      • 1970-01-01
      • 2011-05-04
      • 2014-01-02
      • 2016-12-21
      • 2020-02-18
      • 1970-01-01
      • 2017-03-29
      相关资源
      最近更新 更多