我正在阅读以下内容:https://en.wikibooks.org/wiki/Haskell/Graph_reduction,并写了以下内容:
棘手的空间泄漏示例:
(\xs -> head xs + last xs) [1..n] (\xs -> last xs + head xs) [1..n]
第一个版本在 O(1)空间上运行。 O(n)中的第二个。
这句话是否正确?我无法看出他们是如何不同的。一旦执行,它们似乎都占用相同的时间和空间,在执行之前,您可以懒惰地确定它们的类型是兼容的。
答案 0 :(得分:2)
执行此操作的一种方法是手动评估表达式,并观察中间表达式的最大长度如何随n
的变化而增长。这比听起来要难得多,因为在你的lambas定义中共享xs
,但我会这样做,希望它会有所帮助。
首先,让我们为head
和last
编写定义:
head :: [a] -> a
head (a:_) = a
last :: [a] -> a
last (a:[]) = a
last (_:as) = last as
现在,有了这个,让我们做第一个:
(\xs -> head xs + last xs) [1..n]
=> let xs = [1..n] in head xs + last xs
=> let xs = 1:[2..n] in head xs + last xs
=> let xs = 1:[2..n] in 1 + last xs
=> 1 + last [2..n]
=> 1 + last (2:[3..n])
=> 1 + last [3..n]
=> 1 + last (3:[4..n])
.
.
.
=> 1 + last (n:[])
=> 1 + n
正如您所看到的,中间步骤中的表达式在其大小上有一些不变的上限,而n
的值无关紧要。现在让我们试试你的第二个例子。我选择通过在let
中增加绑定列表来说明结构共享和强制:
(\xs -> last xs + head xs) [1..n]
=> let xs = [1..n] in last xs + head xs
=> let xs = 1:[2..n] in last xs + head xs
=> let xs = 1:xs2
xs2 = 2:[3..]
in last xs2 + head xs
=> let xs = 1:xs2
xs2 = 2:xs3
xs3 = 3:[4..]
in last xs3 + head xs
.
.
.
=> let xs = 1:xs2
xs2 = 2:xs3
xs3 = 3:xs4
.
.
.
xsn = n:[]
in last xsn + head xs
=> let xs = 1:xs2
xs2 = 2:xs3
xs3 = 3:xs4
.
.
.
xsn = n:[]
in n + head xs
=> n + 1
如您所见,最大表达式的大小将与n
成比例。这意味着评估使用O(n)空间。
两个示例之间的区别在于+
函数在强制第二个参数之前强制其第一个参数这一事实。作为一般规则,当你有一个具有多个参数的函数时,必须在其他参数之前强制它们之一,并且它发生的顺序可以产生类似这些的微妙效果。