首页 > 解决方案 > 使用 `foldl` 实现 Haskell 的 `take` 函数

问题描述

使用 .实现 Haskell 的takedrop函数foldl

关于如何使用 实现取放功能的任何建议foldl

take x ls = foldl ???

drop x ls = foldl ???

我已经尝试过这些,但它显示错误:

myFunc :: Int -> [a] -> [a]
myFunc n list = foldl func [] list
    where 
    func x y | (length y) > n = x : y 
             | otherwise      = y

产生错误:

*** Expression : foldl func [] list
*** Term : func
*** Type : a -> [a] -> [a]
*** Does not match : [a] -> [a] -> [a]
*** Because : unification would give infinite type

标签: listhaskellfoldfoldleft

解决方案


做不到。

左折叠必然在无限列表上发散,但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。catamorphismreducer 只能访问xr

折叠通常编码变态,但这表明正确的折叠也可以用来编码超态。就是这样通用的。我认为它很漂亮。

至于类型错误,要修复它只需将参数切换到您的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以这种方式实现肯定会(?)更加笨拙。可能是一个很好的锻炼。


推荐阅读