Haskell 哈斯克尔';foldr是否始终采用双参数λ?

Haskell 哈斯克尔';foldr是否始终采用双参数λ?,haskell,fold,Haskell,Fold,我是哈斯克尔·纽伯 我在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

我是哈斯克尔·纽伯

我在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?如果没有,这个约定是多余的吗?公式可以用更少的代码编写吗?

与haskell中的所有内容一样,看看这些内容的类型,以指导您如何在
ghci
中为任何函数执行此操作

看看foldr的这个,我们看到:

Prelude> :t foldr
foldr :: (a -> b -> b) -> b -> [a] -> b
这个稍微抽象的字符串可以用英语写成:

foldr
是一个需要

1)具有两个参数的函数,一个为
a
类型,另一个为
b
类型,返回
b

2)类型为
b

3)类型为
A

并返回类型为
b


其中,
a
b
是类型变量(有关它们的好教程,请参阅),可以用您喜欢的任何类型填充。

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

如果我们有一个函数

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

我们要将
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->w
a~x
所以
g
的类型必须是

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

foldr g :: (z -> w) -> [x] -> (z -> w)
这当然不是不可能的,尽管可能性更大。我们的累加器是一个函数,对我来说,这需要用扩散列表来演示:

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
返回一个函数,我们将该函数应用于
[]
以反转列表。在本例中,我们为名为
DiffList
[a]->[a]
类型的函数提供了一个别名,但这与编写

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

这是一个由三个参数组成的函数。

事实证明,使用带有三个参数的
foldr
函数,您可以解决
压缩
问题

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
。实际上,我们正在传递一个二进制函数,它恰好返回一个函数

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
现在让我们关注类型:

f :: a -> (a -> [a]) -> (a -> [a])
f    x    k
因此,
x::a
是我们要折叠的列表的元素。函数
k
是列表尾部折叠的结果。
fxk
的结果与
k
的类型相同

\w -> if ....    :: (a -> [a])
这个匿名函数背后的总体思想如下。参数
k
的作用与OP代码中的
acc
相同,只是它是一个函数,在生成累积压缩列表之前,需要列表中的前一个元素
w

具体地说,当我们使用
acc
时,我们现在使用
kx
,将当前元素传递到下一步,因为到那时
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有点奇怪:上面的代码可以说不如显式递归可读。

您可以想象许多foldr使用两个以上参数的实例,例如,
foldr(\f g x->f(g x))(\x->x)
。我不确定是否会使用
foldr
来实现您的
compress
功能。如果参数是空列表,会发生什么情况呢?
foldr
实现会很好,您只需要先有一个模式来处理空列表的情况
compress'[]=[]
@SimonGibbons,另一种适用于任何
可折叠的
,并且与列表融合很好的方法是对累加器使用
可能
。谢谢,这有助于我理解这个答案。但是顺便说一下,将最后两行更改为
其中fxk=\w->如果x==w,那么kw else x:kw
似乎不起作用。我明白了:
*Main>compress[2,3,3]=>[2,3,3]
你能帮我理解一下
f x k w
的返回值也是一个函数吗?如果你说累加器是一个函数,这不意味着它在整个折叠过程中都是一个函数吗?在你的例子中,如果
k
是一个带一个参数的函数,那么
kx
x:kx
已经包含了这个参数,而且据我所知,累加器每次都成为
f
的返回值,它不是变成了
kx
(或者
x:kx
),这只是一个列表,而不是