我认为它没有。
我的原因是 Haskell 是纯函数式编程(没有 I/O Monad),如果“名称”相同,他们可以让每个“按名称调用”使用相同的评估值。
我对实现细节一无所知,但我真的很感兴趣。
详细的解释将不胜感激:)
顺便说一句,我试过谷歌,很难得到任何有用的东西。
我认为它没有。
我的原因是 Haskell 是纯函数式编程(没有 I/O Monad),如果“名称”相同,他们可以让每个“按名称调用”使用相同的评估值。
我对实现细节一无所知,但我真的很感兴趣。
详细的解释将不胜感激:)
顺便说一句,我试过谷歌,很难得到任何有用的东西。
首先,Haskell 是一个规范,而不是一个实现;该报告实际上并不要求使用点名评估或惰性评估。Haskell 实现只需要是非严格的,这确实排除了按值调用和类似策略。
所以,严格来说(哈,哈),评估策略不能减慢 Haskell 的速度。我不确定什么会减慢 Haskell 的速度,尽管很明显有些东西已经发生了,否则在 Haskell 98 之后发布下一个版本的报告不会花费 12 年。我的猜测是它以某种方式涉及委员会。
无论如何,“惰性评估”是指“按需调用”策略,这是 Haskell 最常见的实现选择。这与按名称调用的不同之处在于,如果一个子表达式在多个地方使用,它最多会被计算一次。
有资格作为共享子表达式的细节有点微妙,可能有点依赖于实现,但使用 GHC Haskell 中的示例:考虑函数cycle
,它无限重复输入列表。一个天真的实现可能是:
cycle xs = xs ++ cycle xs
这最终导致效率低下,因为没有cycle xs
可以共享的单个表达式,因此结果列表必须在遍历时不断构建,每次分配更多内存并进行更多计算。
相比之下,实际的实现是这样的:
cycle xs = xs' where xs' = xs ++ xs'
在这里,名称xs'
被递归地定义为自身附加到输入列表的末尾。这个时间xs'
是共享的,并且只评估一次;生成的无限列表实际上是内存中的有限循环链表,一旦评估了整个循环,就不需要进一步的工作了。
一般来说,GHC 不会为你记住函数:给定f
和x
,每次使用f x
都会被重新评估,除非你给结果一个名字并使用它。在任何一种情况下,结果值都是相同的,但性能可能会有很大差异。这主要是为了避免悲观——GHC 很容易为你记住事情,但在许多情况下,这将花费大量内存来获得微小或根本不存在的速度。
另一方面是保留了共享值。如果您有一个计算成本非常高的数据结构,则命名构造它的结果并将其传递给使用它的函数可确保不会重复任何工作——即使它被不同的线程同时使用。
你也可以用这种方式自己悲观——如果一个数据结构计算成本低并且使用大量内存,你可能应该避免共享对完整结构的引用,因为这将使整个事物在内存中保持活力,只要有任何可能以后可能会用到。
是的,在某种程度上确实如此。问题是 Haskell 通常不能过早地计算值(例如,如果它会导致异常),所以它有时需要保留一个 thunk(用于计算值的代码)而不是值本身,这使用更多内存并减慢速度。编译器尝试检测可以避免这种情况的情况,但不可能检测到所有情况。