我有两个与filter
和takeWhile
类似的功能。
filterAcc, takeWhileAcc :: ([a] -> Bool) -> [a] -> [a]
filterAcc p xs = go xs []
where go [] acc = acc
go (x:xs) acc
| p (x:acc) = go xs (x:acc)
| otherwise = go xs acc
takeWhileAcc p xs = go xs []
where go [] acc = acc
go (x:xs) acc
| p (x:acc) = go xs (x:acc)
| otherwise = acc
它们都采用谓词和列表,它们与常规filter
和takeWhile
不同,因为谓词将累积结果作为输入。
我的问题是,filter even [1..]
会立即(懒惰地)开始生成输出,filterAcc (any even) [1..]
会挂起。我怀疑帮助函数go
阻止这些函数懒散地行动。
如何让这些功能懒散地运作?
答案 0 :(得分:6)
问题在于go
的缺点总是以对自身的尾调用结束。当它到达列表的末尾时,它只返回一些有用的东西,当然,它永远不会出现在无限列表中。
相反,您应该随时返回元素:
filterAcc, takeWhileAcc :: ([a] -> Bool) -> [a] -> [a]
filterAcc p xs = go xs []
where go [] acc = []
go (x:xs) acc
| p (x:acc) = x : go xs (x:acc)
| otherwise = go xs acc
takeWhileAcc p xs = go xs []
where go [] acc = []
go (x:xs) acc
| p (x:acc) = x : go xs (x:acc)
| otherwise = []
答案 1 :(得分:1)
懒惰列表消费通常由foldr
实现。
您的累加器需要从左到右的信息流。这通常通过使用foldl
来实现,但这意味着严格的列表消耗。
解决方案是使用scanl
:
--- mapAccumL :: (acc -> x -> (acc, y)) -> acc -> [x] -> (acc, [y])
--- scanl :: (a -> b -> a) -> a -> [b] -> [a]
takeWhileAcc p [] = []
takeWhileAcc p (x:xs) = map snd $ takeWhile (p.fst)
$ scanl (\(acc,_) y-> (y:acc,y)) ([x],x) xs
filterAcc p [] = []
filterAcc p (x:xs) = map snd $ filter (p.fst)
$ scanl (\(acc,_) y-> (y:acc,y)) ([x],x) xs
另一种可能性是使用until
或mapAccumL
。后者将是一个自然的拟合,除了它不收集累积值,而是传递最后一个累加器值。