【问题标题】:To what extent is Haskell lazy?Haskell 的懒惰到什么程度?
【发布时间】:2015-03-31 10:01:52
【问题描述】:

我需要澄清一下 Haskell 的懒惰。

如果我有这个功能:

myFunction arg
  | arg == 1 = a
  | arg == 2 = a*b
  | arg == 3 = b+c
  | otherwise = (a+b)*c
     where
            a = ...
            b = ...
            c = ...
            d = ...

当我调用myFunction 1 时,Haskell 将只评估 a = ... 函数,既不评估 b 也不评估 c 也不评估 d

但是如果我写

myFunction arg
  | arg == 1 = a
  | arg == 2 = a*b
  | arg == 3 = b+c
  | otherwise = (a+b)*c
     where
            (a,b,c,d) = anotherFunction arg

Haskell 的行为会是什么?

  • 它会仅评估 a 并将惰性“传播”到anotherFunction吗?
  • 或者,它会评估整个元组 (a,b,c,d) 作为anotherFunction的结果吗?

【问题讨论】:

  • 它将评估元组x = anotherFunction arg,但不会评估元组的所有元素
  • 当您说“致电myFunction 1”时,我假设您的意思是在评估该表达式时。正如 Zeta 所说,它将评估元组(但不是元素),然后从该元组评估 a
  • @Zeta 元组 b,cd 的其他值将是 thunk 形式。或者有更合适的词吗?
  • @Sibi:我想说元组将是 weak head normal form 更合适,但是 thunk 是可以的。
  • @Zeta 谢谢,已适当更新。

标签: haskell lazy-evaluation


【解决方案1】:

在这两种情况下,除非需要该值,否则它不会评估任何内容。要求该值的一种方法是在 ghci 中调用该函数(它在ghci 中打印该值,因此要求它)。假设您正在执行该函数,那么在您的第二种情况下,它会将元组评估为weak head normal form (WHNF),然后评估(a,b,c,d) 中的第一个元素,因为只需要该值。其他元素 bcd 将采用 thunk 形式。事实上,您可以验证自己:

myFunction arg
  | arg == 1 = a
  | arg == 2 = a*b
  | arg == 3 = b+c
  | otherwise = (a+b)*c
  where
    (a,b,c,d) = anotherFunction arg

anotherFunction x = (x, undefined, undefined, undefined)

ghci 中的演示:

λ> myFunction 1
1

【讨论】:

  • @JeanJouX undefineds 在测试语言的非严格性方面非常有效。
  • @thefourtheye 如果当其中一个输入未定义时某些东西起作用,这意味着程序从未尝试评估它。
【解决方案2】:

它只对a感兴趣,所以这意味着有一个隐式函数

thea :: (a,b,c,d) -> a
thea (a,_,_,_) = a

换句话说,Haskell 对元组的其他元素不感兴趣。然而,有时元组的元素共享一些结构。假设另一个函数定义为:

anotherFunction :: Int -> (Int,Int,Int,Int)
anotherFunction x = (z,t,f,g)
    where f = x*x
          g = f+x
          z = g-2
          t = 3

在这种情况下 - 为了评估第一个元素 - 第三个和第四个元素也将被评估。但由于您不对它们做任何事情,Haskell 不会对它们的结果特别感兴趣。

【讨论】:

    【解决方案3】:

    正如其他人已经指出的那样,只会评估 a

    但请记住,要利用惰性,anotherFunction 在评估其组件之前返回一个元组至关重要。例如,考虑

    anotherFunction n = if p > 1000 then (n, p) else (n, 0)
      where p = product [1..n]
    

    即使调用者只需要第一个对组件(即n),上述内容也将始终评估product [1..n]。这是因为if 需要在返回对之前进行评估,这会强制p。相比之下,

    anotherFunction n = (n, if p > 1000 then p else 0)
      where p = product [1..n]
    

    将立即返回该对。如果只计算其第一个组件,则根本不会计算 p

    【讨论】:

      【解决方案4】:

      除非需要获取该变量的值,否则不会对其进行评估。基本上 Haskell 很懒惰,除非被告知不要这样做。

      你可以确认一下,像这样

      Prelude> :set +m
      Prelude> let anotherFunction = (100, 1 `div` 0)
      Prelude| 
      Prelude> let myFunction arg
      Prelude|                | arg == 1  = a
      Prelude|                | otherwise = b
      Prelude|                where
      Prelude|                     (a, b) = anotherFunction
      Prelude| 
      

      在这里,1 `div` 0 将引发 divide by zero 错误。如果它评估所有元素,那么即使您使用1 调用myFunction,您也会收到该错误,但是

      Prelude> myFunction 1
      100
      

      仅当您使用任何其他值调用它时,才需要评估元组的第二个值,它会失败并出现divide by zero 错误。

      Prelude> myFunction 2
      *** Exception: divide by zero
      

      【讨论】:

        猜你喜欢
        • 2015-09-06
        • 1970-01-01
        • 2015-09-06
        • 1970-01-01
        • 1970-01-01
        • 2019-10-15
        • 1970-01-01
        • 2011-11-21
        • 2016-10-21
        相关资源
        最近更新 更多