Haskell 懒到什么程度?
To what extent is Haskell lazy?
我需要澄清一下 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 和 'propagate' 对
anotherFunction
的惰性吗?
- 或者,它会评估整个元组 (a,b,c,d) 作为
anotherFunction
的结果吗?
嗯,它只对 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 不会特别关注它们的结果。
在这两种情况下,除非需要值,否则它不会计算任何值。要求该值的一种方法是调用 ghci 中的函数(它打印 ghci
中的值并因此要求它)。假设您正在执行该函数,那么在您的第二种情况下,它会将元组计算为 weak head normal form (WHNF),然后计算 (a,b,c,d)
中的第一个元素,因为只需要该值。其他元素 b
、c
和 d
将采用 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
除非需要获取该变量的值,否则不会对其求值。基本上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
正如其他人已经指出的那样,只会评估 a
。
但是请记住,要利用惰性,在评估其组件之前 anotherFunction
return 是一个元组是至关重要的。例如,考虑
anotherFunction n = if p > 1000 then (n, p) else (n, 0)
where p = product [1..n]
即使调用者只需要第一对组件(即 n
),上面的代码也会始终计算 product [1..n]
。这是因为 if
需要在 returned 对之前进行评估,这会强制 p
。相比之下,
anotherFunction n = (n, if p > 1000 then p else 0)
where p = product [1..n]
将立即 return 这对。如果仅计算其第一个组件,则根本不会计算 p
。
我需要澄清一下 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 和 'propagate' 对
anotherFunction
的惰性吗? - 或者,它会评估整个元组 (a,b,c,d) 作为
anotherFunction
的结果吗?
嗯,它只对 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 不会特别关注它们的结果。
在这两种情况下,除非需要值,否则它不会计算任何值。要求该值的一种方法是调用 ghci 中的函数(它打印 ghci
中的值并因此要求它)。假设您正在执行该函数,那么在您的第二种情况下,它会将元组计算为 weak head normal form (WHNF),然后计算 (a,b,c,d)
中的第一个元素,因为只需要该值。其他元素 b
、c
和 d
将采用 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
除非需要获取该变量的值,否则不会对其求值。基本上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
正如其他人已经指出的那样,只会评估 a
。
但是请记住,要利用惰性,在评估其组件之前 anotherFunction
return 是一个元组是至关重要的。例如,考虑
anotherFunction n = if p > 1000 then (n, p) else (n, 0)
where p = product [1..n]
即使调用者只需要第一对组件(即 n
),上面的代码也会始终计算 product [1..n]
。这是因为 if
需要在 returned 对之前进行评估,这会强制 p
。相比之下,
anotherFunction n = (n, if p > 1000 then p else 0)
where p = product [1..n]
将立即 return 这对。如果仅计算其第一个组件,则根本不会计算 p
。