做不到。
左折叠必然在无限列表上发散,但take n
不会。这是因为左折叠是尾递归的,所以它必须扫描整个输入列表才能开始处理。
用正确的折叠,它是
ntake :: Int -> [a] -> [a]
ntake 0 _ = []
ntake n xs = foldr g z xs 0
where
g x r i | i>=n = []
| otherwise = x : r (i+1)
z _ = []
ndrop :: Int -> [a] -> [a]
ndrop 0 xs = xs
ndrop n xs = foldr g z xs 0 xs
where
g x r i xs@(_:t) | i>=n = xs
| otherwise = r (i+1) t
z _ _ = []
ndrop
很好地忠实地实现了一个paramorphism,直到reducer函数的参数顺序g
,使它可以访问当前元素x
和当前列表节点xs
(例如xs == (x:t)
)以及递归结果r
。catamorphism的reducer 只能访问x
和r
。
折叠通常编码变态,但这表明正确的折叠也可以用来编码超态。就是这样通用的。我认为它很漂亮。
至于类型错误,要修复它只需将参数切换到您的func
:
func y x | ..... = .......
左折叠中的累加器作为reducer 函数的第一个参数。
如果你真的想用左折叠完成,并且你真的确定列表是有限的,有两个选择:
ltake n xs = post $ foldl' g (0,id) xs
where
g (i,f) x | i < n = (i+1, f . (x:))
| otherwise = (i,f)
post (_,f) = f []
rltake n xs = foldl' g id xs r n
where
g acc x = acc . f x
f x r i | i > 0 = x : r (i-1)
| otherwise = []
r _ = []
第一个从左往上计数,可能会停止在完整列表遍历中间组装前缀,但它确实携带到最后,是左折叠。
第二个也完全遍历列表,将其转换为右折叠,然后再次从左侧倒计时开始工作,一旦组装前缀就能够真正停止工作。
drop
以这种方式实现肯定会(?)更加笨拙。可能是一个很好的锻炼。