Chr*_*ris 7 evaluation haskell
我对Haskell很新,在阅读了这篇以及关于严格性的一些性能提示后,我仍然想知道这是如何适用于let和where表达式的.如果我有以下代码:
f :: Int -> Int -> Int
f a b
|a==b = <simple computation>
|otherwise = e1 + 2 * e1 - e1^2
where e1 = <lengthy computation>
Run Code Online (Sandbox Code Playgroud)
经常<lengthy computation>评估的频率如何?我假设如果给出Haskell的懒惰评估,e1则根本不进行评估a==b.但是,如果没有,则e1在otherwise表达式中替换,然后在每次遇到它时进行评估,或者在第一次遇到它时进行评估,然后在所有后续事件中进行存储和重用?也:
-o吗?这与这个问题非常相似,但我找不到Haskell的答案.
解释非常感谢.
通常,常量应用形式where或let块中的代码仅被评估一次,并且仅在必要时被评估(即,如果它根本不被使用它也将根本不被评估).
f不是一个恒定的应用形式,因为它有论据; 它相当于
f' = \a b -> let e1 = <lengthy computation>
in if a==b
then <simple computation>
else e1 + 2 * e1 - e1^2
Run Code Online (Sandbox Code Playgroud)
因此,每次使用两个参数调用函数时都会e1评估一次.这也可能也是你想要的,事实上,如果<lengthy computation>取决于两者a而且最好的行为可能b.如果它只依赖于a,你可以做得更好:
f? a = \b ->
if a==b then <simple computation>
else e1 + 2 * e1 - e1^2
where e1 = <lengthy computation>
Run Code Online (Sandbox Code Playgroud)
当您执行以下操作时,此表单将更有效map (f 34) [1,3,9,2,9]:在该示例中,e1仅对整个列表计算一次.(但<lengthy computation>不会有b范围,所以它不能依赖它.)
OTOH,也可能存在你根本不想 e1被保留的情况.(例如,如果它占用大量内存,但计算速度相当快).在这种情况下,你可以把它变成一个"nullary函数"
f? a b
| a==b = <simple computation>
| otherwise = e1() + 2 * e1() - e1()^2
where e1 () = <lengthy computation>
Run Code Online (Sandbox Code Playgroud)
默认情况下,函数不会被记忆,因此在上面,<lengthy computation>如果是三次,则执行零次a==b.
另一种可能性是强制e1总是只评估一次.你可以这样做seq:
f? a b = e1 `seq` if a==b
then <simple computation>
else e1 + 2 * e1 - e1^2
where e1 = <lengthy computation>
Run Code Online (Sandbox Code Playgroud)
这是实际上改变了语义的一些建议,而不仅仅是性能:假设我们总是定义e1 = error "too tough".然后f,f',f?并f?都将仍然工作提供a==b; 但f?在这种情况下甚至会失败.
至于最佳化(-O或-O2) -这些通常不会改变你的程序的严格性(即不能的行为之间的任何改变f和f?).除此之外,编译器可以自由地进行任何它认为对性能有益的更改.但通常情况下,它不会改变我上面所说的内容.正如Taren评论的那样,主要的例外是f?:编译器将很容易内联e1 ()然后共享对计算值的引用,这会阻止垃圾收集器回收内存.因此,最好不要依赖这种(无论如何有点hackish)技术.