Haskell的懒惰评估永远不会比急切的评估采取更多的评估步骤.
另一方面,Scala的逐个名称评估可能需要比按值调用更多的评估步骤(如果短路效益大于由重复计算的成本抵消).
我认为按名称呼叫大致相当于懒惰的评估.为什么那么时间上的这种差异保证了?
我猜测也许Haskell语言指定在评估期间必须使用memoization; 但在那种情况下,为什么Scala不这样做呢?
评估策略的名称有一定的广度,但它们大致如下:
按名称调用一个参数几乎只是在调用函数时以任何(未评估的)形式替换到函数体中.这意味着它可能需要在体内多次评估.
在Scala中,您将其写为:
scala> def f(x:=> Int): Int = x + x scala> f({ println("evaluated"); 1 }) evaluated evaluated 2
在Haskell中,您没有内置的方法来执行此操作,但您始终可以将按名称调用值表示为类型的函数() -> a
.这有点模糊,但由于参考透明度 - 你将无法像Scala一样测试它(并且编译器可能会优化掉你的"名字"部分).
按需调用(lazy ... sort)调用函数时不会计算参数,但是第一次需要.那一刻,它也被缓存了.之后,只要再次需要参数,就会查找缓存的值.
在Scala中,你没有声明你的函数参数是懒惰的,你做一个懒惰的声明:
scala> lazy x: Int = { println("evaluated"); 1 } scala> x + x evaluated 2
在Haskell中,这是默认情况下所有函数的工作方式.
调用函数时,按值调用(渴望,几乎每种语言都会这样做)参数被计算,即使函数最终没有使用这些参数.
在Scala中,这是默认情况下函数的工作方式.
scala> def f(x: Int): Int = x + x scala> f({ println("evaluated"); 1 }) evaluated 2
在Haskell中,您可以在函数参数上使用bang模式强制执行此行为:
ghci> :{ ghci> f :: Int -> Int ghci> f !x = x ghci> :}
因此,如果需要调用(懒惰)执行或多或少的评估(作为其他策略之一),为什么要使用其他任何东西?
懒惰的评价是很难推理,除非你有引用透明,因为你需要准确地计算出,当你懒的价值进行评估.由于Scala是为与Java互操作而构建的,因此它需要支持命令式的,有效的编程.因此,在许多情况下,在Scala中使用它并不是一个好主意lazy
.
此外,lazy
还有一个性能开销:您需要有一个额外的间接检查以检查该值是否已经过评估.在Scala中,这会转换为更多的对象,这会给垃圾收集器带来更大的压力.
最后,有些情况下,懒惰评估会留下"空间"泄漏.例如,在Haskell中,通过将它们加在一起从右侧折叠大量数字是一个坏主意,因为Haskell会(+)
在评估它们之前建立这个庞大的一系列惰性调用(实际上你只需要它有一个累加器)你在简单的环境中得到甚至空间问题的一个著名的例子就是foldr
VS foldl
VSfoldl'
.