使用`foldl`实现Haskell的`take`函数

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用`foldl`实现Haskell的`take`函数相关的知识,希望对你有一定的参考价值。

使用take实现Haskell的dropfoldl函数。

有关如何使用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
答案

无法做到。

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

折叠通常编码catamorphisms,但这表明右折叠也可以用来编码一个paramorphism。这种方式很普遍。我觉得它很漂亮。

至于类型错误,要修复它只需将参数切换到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必然会(?)甚至更笨拙。可能是一个很好的运动。

另一答案

我注意到你从未指定折叠必须在提供的列表上。因此,满足您的问题的一个方法,虽然可能不是精神,但是:

sillytake :: Int -> [a] -> [a]
sillytake n xs = foldl go (const []) [1..n] xs
  where go f _ (x:xs) = x : f xs
        go _ _ []     = []

sillydrop :: Int -> [a] -> [a]
sillydrop n xs = foldl go id [1..n] xs
  where go f _ (_:xs) = f xs
        go _ _ []     = []

这些都使用左侧折叠,但是在数字列表[1..n]上 - 数字本身被忽略,并且列表仅用于其长度来为给定的take n构建自定义drop nn函数。然后将此函数应用于原始提供的列表xs

这些版本在无限列表上运行良好:

> sillytake 5 $ sillydrop 5 $ [1..]
[6,7,8,9,10]
另一答案

你不是太远了。这是一对修复。

首先,请注意func首先传递累加器(即a列表,在您的情况下),然后是list元素(a)。所以,你需要交换func参数的顺序。

然后,如果我们想模仿take,我们需要在x小于length y时添加n,而不是更大!

所以我们得到了

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

测试:

> myFunc 5 [1..10]
[5,4,3,2,1]

如您所见,这正在扭转字符串。这是因为我们在前面(x)而不是在后面(x:y)添加y++[x]。或者,也可以使用reverse (foldl ....)来修复最后的订单。

此外,由于foldl总是扫描整个输入列表,myFunc 3 [1..1000000000]将花费大量时间,而myFunc 3 [1..]将无法终止。使用foldr会好得多。

drop更难处理。如果没有像myFunc n xs = fst (foldl ...)这样的后处理或者让foldl返回一个你立即调用的函数(这也是一种后处理),我认为你不能轻易做到这一点。

另一答案

Ness是否会用take实现foldr。用drop实现foldr最不令人厌恶的方式是这样的:

drop n0 xs0 = foldr go stop xs0 n0
  where
    stop _ = []
    go x r n
      | n <= 0 = x : r 0
      | otherwise = r (n - 1)

如果您别无选择,请考虑效率损失并重建整个列表!最好用螺丝刀钉入钉子,而不是用锤子拧入螺钉。

两种方式都很可怕。但是这个可以帮助您了解折叠如何用于构造函数以及它们的限制。

Folds只是不适合实施drop的工具;一个paramorphism是正确的工具。

以上是关于使用`foldl`实现Haskell的`take`函数的主要内容,如果未能解决你的问题,请参考以下文章

Haskell - 严格与非严格与 foldl

我不确定我是否理解haskell中foldl函数的类型定义

Haskell 的 foldr/l 和 Clojure 的 reduce

Haskell:一个组合案例

为啥在 Racket 中以一种奇怪的方式定义 foldl?

Haskell入门篇九:高阶函数(中)