【问题标题】:Does haskell's foldr always take a two-parameter lambda?haskell 的 foldr 是否总是采用两个参数的 lambda?
【发布时间】:2016-06-02 01:45:08
【问题描述】:

这里是 Haskell 新手

我正在用 haskell 解决这个问题:

(**) Eliminate consecutive duplicates of list elements.
If a list contains repeated elements they should be replaced with a single copy of the element. The order of the elements should not be changed.

Example:
* (compress '(a a a a b c c a a d e e e e))
(A B C A D E)

解决方案(我必须查找)使用 foldr:

compress' :: (Eq a) => [a] -> [a]
compress' xs = foldr (\x acc -> if x == (head acc) then acc else x:acc) [last xs] xs

这个foldr,根据解法,有两个参数,x和acc。似乎所有 foldr 都采用这些参数;这有什么例外吗?像一个需要 3 个或更多的文件夹?如果不是,这个约定是不是多余的,公式可以用更少的代码编写吗?

【问题讨论】:

  • 您可以设想许多 foldr 接受超过 2 个参数的情况,例如 foldr (\f g x -> f (g x)) (\x -> x)
  • 我不确定我是否会使用foldr 来实现您的compress 功能。如果参数是空列表会发生什么?
  • foldr 实现会很好,您只需要首先有一个模式可以处理空列表的情况compress' [] = []
  • @SimonGibbons,另一种适用于任何Foldable 并且与列表融合配合良好的方法是使用Maybe 作为累加器。

标签: haskell fold


【解决方案1】:

foldr 采用 2 个参数的函数,但这并不妨碍它采用 3 个参数的函数,前提是该函数具有正确的类型签名。

如果我们有一个函数

g :: x -> y -> z -> w

foldr :: (a -> b -> b) -> b -> [a] -> b

我们想将g 传递给foldr,然后是(a -> b -> b) ~ (x -> y -> z -> w)(其中~ 是类型相等)。由于-> 是右结合的,这意味着我们可以将g 的签名写为

x -> y -> (z -> w)

和它的意思是一样的。现在我们已经生成了一个有两个参数的函数,它返回一个有一个参数的函数。为了将它与a -> b -> b 类型统一起来,我们只需要将参数对齐:

a ->   |  x ->
b ->   |  y -> 
b      |  (z -> w)

这意味着b ~ z -> w,所以y ~ b ~ z -> wa ~ x 所以g 的类型真的必须是

g :: x -> (z -> w) -> (z -> w)

暗示

foldr g :: (z -> w) -> [x] -> (z -> w)

这当然不是不可能的,尽管可能性更大。我们的累加器是一个函数,对我来说,这需要用 DiffLists 来演示:

type DiffList a = [a] -> [a]

append :: a -> DiffList a -> DiffList a
append x dl = \xs -> dl xs ++ [x]

reverse' :: [a] -> [a]
reverse' xs = foldr append (const []) xs $ []

请注意,foldr append (const []) xs 返回一个函数,我们将其应用于[] 以反转列表。在这种情况下,我们为 [a] -> [a] 类型的函数提供了一个别名,称为 DiffList,但这实际上与编写并没有什么不同

append :: a -> ([a] -> [a]) -> [a] -> [a]

这是一个有 3 个参数的函数。

【讨论】:

    【解决方案2】:

    与 haskell 中的所有内容一样,请查看事物的类型,以指导您为ghci 中的任何函数执行此操作。

    查看这个对于 foldr 我们看到:

    Prelude> :t foldr
    foldr :: (a -> b -> b) -> b -> [a] -> b
    

    这个稍微抽象的字符串可以用英文写成:

    foldr 是一个接受

    的函数

    1 ) 具有两个参数的函数,一个是 a 类型,一个是 b 类型,并返回 b 类型的东西

    2 ) b 类型的值

    3 ) a 类型的值列表

    并返回b类型的值

    其中ab 是类型变量(有关它们的优秀教程,请参阅here),可以用您喜欢的任何类型填写。

    【讨论】:

      【解决方案3】:

      事实证明,您可以使用具有三参数函数的foldr 来解决您的compress 问题。

      compress :: Eq a => [a] -> [a]
      compress [] = []
      compress (z:zs) = z : foldr f (const []) zs z
        where f x k w | x==w      = k x
                      | otherwise = x : k x
      

      让我们剖析一下。首先,我们可以通过将最后两行改为

      来提高可读性
        where f x k = \w -> if x==w then k x else x : k x
      

      这表明三元函数只不过是返回一元函数的二元函数。以这种方式查看它的好处是foldr 在传递二进制函数时最容易理解。事实上,我们正在传递一个二进制函数,而它恰好返回一个函数。

      现在让我们关注类型:

      f :: a -> (a -> [a]) -> (a -> [a])
      f    x    k
      

      所以,x::a 是我们要折叠的列表的元素。函数k 是列表尾部折叠的结果。 f x k 的结果与 k 的类型相同。

      \w -> if ....    :: (a -> [a])
      

      这个匿名函数背后的总体思路如下。参数k 与OP 代码中的acc 的作用相同,只是它是一个函数,在生成累积压缩列表之前需要列表中的previous 元素w

      具体来说,当我们使用acc 时,我们现在使用k x,将当前元素传递到下一步,因为到那时x 将成为前一个元素w。在顶层,我们将z 传递给foldr f (const []) 返回的函数。

      这个compress 变体是惰性的,与发布的解决方案不同。实际上,发布的解决方案需要在开始生成某些内容之前扫描整个列表:这是由于(\x acc -> ...)acc 中的严格,以及last xs 的使用。相反,上面的压缩输出列表元素在一种“流式”方式。事实上,它也适用于无限列表:

      > take 10 $ compress [1..]
      [1,2,3,4,5,6,7,8,9,10]
      

      话虽如此,我认为在这里使用foldr 感觉有点奇怪:上面的代码可以说比显式递归的可读性差。

      【讨论】:

      • 谢谢,这有助于我理解这个答案,stackoverflow.com/questions/37526740/…。但顺便说一句,按照您对where f x k = \w -> if x==w then k w else x : k w 的建议更改最后两行似乎不起作用。我明白了:*Main> compress [2,3,3] => [2,3,3]
      • 能否请您帮助我理解f x k w 的返回值如何也是一个函数 - 如果您说累加器是一个函数,这是否意味着它在整个折叠过程中都是一个函数?如果在您的示例中,k 是一个接受一个参数的函数,那么k xx : k x 已经包含该参数,并且据我所知,累加器每次都成为f 的返回值,不不就是变成k x(或者x : k x),只是一个列表,而不是一个函数?
      • 我写了k w,但我应该写k x -- 已编辑。
      • 不,k x 是一个列表。 k 是一个函数。累加器仍然是一个函数,因为它从k(一个函数)变为\w -> if ...(另一个函数,也是f x k
      • @AleXoundOS 我不这么认为,因为foldM 的结果在一个单子中,所以返回m (a->b) 不会让我们通过第三个参数。我们可以将其转换为a -> m b,但在这种情况下,一元效应不能依赖于a,所以它并不完全通用。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多