将`let`和`where`表达式的结果存储在haskell中吗?

Chr*_*ris 7 evaluation haskell

我对Haskell很新,在阅读了这篇以及关于严格性的一些性能提示后,我仍然想知道这是如何适用于letwhere表达式的.如果我有以下代码:

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.但是,如果没有,则e1otherwise表达式中替换,然后在每次遇到它时进行评估,或者在第一次遇到它时进行评估,然后在所有后续事件中进行存储和重用?也:

  • 有没有办法"手动"控制这个过程?
  • 这取决于天气我在ghci中运行代码或用GHC编译它并在GHC编译中它依赖于像这样的标志-o吗?

这与这个问题非常相似,但我找不到Haskell的答案.

解释非常感谢.

lef*_*out 9

通常,常量应用形式wherelet块中的代码被评估一次,并且仅在必要时被评估(即,如果它根本不被使用它也将根本不被评估).

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) -这些通常不会改变你的程序的严格性(即不能的行为之间的任何改变ff?).除此之外,编译器可以自由地进行任何它认为对性能有益的更改.但通常情况下,它不会改变我上面所说的内容.正如Taren评论的那样,主要的例外是f?:编译器将很容易内联e1 ()然后共享对计算值的引用,这会阻止垃圾收集器回收内存.因此,最好不要依赖这种(无论如何有点hackish)技术.

  • 如果e1不依赖于a或b我会期望ghc将其浮出来以便记忆,至少使用-O2.编辑:即使使用-O它会将整个rhs浮动到顶层:`f2 :: Int; f2 = case $ w $ sg 10000 #ww {__DEFAULT - >我#( - #(+#ww(*#2#ww))(*#ww ww))}`Ghc很高兴到了这一点它可能很难阻止它这样做.只是让e1成为一个需要单位的函数通常不足以阻止它被这样的记忆. (5认同)
  • @leftaroundabout用-O2尝试`gi = [1..i]`和`e1 = g 10000000`.Ghc为我浮出来导致~600mb峰值内存使用量. (2认同)