[英]Haskell function parameter force evaluation
我需要从列表中获取最后n
元素,使用O(n)
内存,所以我编写了这段代码
take' :: Int -> [Int] -> [Int]
take' n xs = (helper $! (length $! xs) - n + 1) xs
where helper skip [] = []
helper skip (x : xs) = if skip == 0 then xs else (helper $! skip - 1) xs
main = print (take' 10 [1 .. 100000])
这段代码需要O(|L|)
内存,其中|L|
- 是给定列表的长度。
但是当我写这段代码时
take' :: Int -> [Int] -> [Int]
take' n xs = helper (100000 - n + 1) xs
where helper skip [] = []
helper skip (x : xs) = if skip == 0 then xs else (helper $! skip - 1) xs
main = print (take' 10 [1 .. 100000])
这段代码现在只需要O(n)
内存(唯一的chage是(helper $! (length $! xs) - n + 1)
- > helper (100000 - n + 1)
)
因此,据我所知,Haskell由于某种原因在第一次调用helper
之前没有评估length xs
,所以它在skip
留下了一个thunk并且haskell必须在每个堆栈帧中保留这个值而不是进行尾递归。 但是在第二段代码中,它会评估(100000 - n + 1)
并为helper
提供纯值。
所以问题是如何在第一次调用helper之前评估列表的长度,并且只使用O(n)
内存。
另一个答案提到成为一个好消费者意味着什么。 您已经发布了两个版本的函数,一个适用于任意长度的列表,但不是一个好的消费者,一个是一个很好的消费者,但假定一个特定的列表长度。 为了完整性,这里有一个功能很好的消费者,适用于任意列表长度:
takeLast n xs = go (drop n xs) xs where
go (_:xs) (_:ys) = go xs ys
go _ ys = ys
第二个版本并不真正只占用O ( n )内存。 无论take'
什么take'
:你从一个长度为L的列表开始,并且必须存储在某个地方。
它有效占用O ( n )内存的原因是该列表仅由一个“好消费者”使用,即helper
。 这样的消费者从头到尾解构清单; 因为在其他任何地方都不需要对头部的引用,垃圾收集器可以立即开始清理那些第一个元素 - 在列表理解之前甚至已经建立了列表的其余部分!
但是,如果在使用helper
之前,您会计算该列表的length
。 这已经迫使整个列表成为NF'd † ,正如我所说,这不可避免地需要O ( L )内存。 因为您仍然持有与helper
一起使用的引用,所以在这种情况下,垃圾收集器在整个列表在内存之前不能执行任何操作。
所以,它与严格的评估无关。 实际上,实现目标的唯一方法是使其不那么严格(只需要在任何给定时间评估长度为n的子列表)。
† 更确切地说:它强制列表的脊椎为正常形式。 不评估元素,但它仍然是O ( L )。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.