Haskell 哈斯克尔';foldr是否始终采用双参数λ?
我是哈斯克尔·纽伯 我在haskell研究这个问题: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
(**) 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
),这只是一个列表,而不是