我正在锻炼Haskell 99 questions。
问题8
消除列表元素的连续重复。
如果列表包含重复元素,则应将其替换为 单个副本的元素。元素的顺序不应该是 改变。
Haskell中的示例:
> compress "aaaabccaadeeee" "abcade"
我无法理解这个解决方案:
compress xs = foldr f (const []) xs Nothing
where
f x r a@(Just q) | x == q = r a
f x r _ = x : r (Just x)
foldr
有三个参数。第一个参数是函数(a -> b -> b)
。第二个是初始累加器,第三个是列表。
(const [])
是否将第二个参数传递给foldr
?
函数f :: Eq a => a -> (Maybe a -> [a]) -> Maybe a -> [a]
有三个参数,与foldr
的预期值不匹配。传递了什么值?
最后Nothing
是为了什么?
答案 0 :(得分:6)
你说
foldr
需要三个参数
这是误导。 foldr
的类型是
(a -> r -> r) -> r -> [a] -> r
其中r
是一个类型变量,可以通过任何类型实例化,包括函数类型。这就是这里发生的事情,并且提示确实是由于传递给foldr
的第二个参数(应该与r
匹配)是const []
。
此处使用foldr
的类型如下:
foldr :: Eq a => (a -> (Maybe a -> [a]) -> Maybe a -> [a]) -> (Maybe a -> [a]) -> [a] -> Maybe a -> [a]
foldr :: (a -> r -> r ) -> r -> [a] -> r
一旦您知道r
被实例化为Maybe a -> [a]
,就会发现f
在这里有效地采用了三个参数,foldr
需要四个参数。
同样现象的一个更简单的例子是:
id id 2
在这里,id
似乎有两个参数。外部id
用于函数类型,内部id
用于Integer
参数。
答案 1 :(得分:5)
正如Jubobs所指出的那样,f
具有签名f :: Eq a => a -> (Maybe a -> [a]) -> (Maybe a -> [a])
在foldr
签名的上下文中,这意味着a
为a
,b
是(Maybe a -> [a])
。所以foldr
确实构建了一个复杂的函数。
让我们看看列表"aab"
,这是一个非常简单的输入。折叠扩展到
(f 'a' (f 'a' (f 'b' (const []))))
将Nothing
传递给该构造,您可以开始扩展它。最外面的呼叫有
x = 'a'
r = <the inner stuff>
a@(Just q) fails to match Nothing
_ matches Nothing
所以它的结果是'a' : r (Just 'a')
。用r
代替我们:
'a' : f 'a' (f 'b' (const [])) (Just 'a')
f
新的最外层电话有
x = 'a'
r = (f 'b' (const []))
a@(Just q) matches Just 'a'
guard 'a' == 'a' is true
所以r a
分支被采用,即它只是转发到链中的下一个分支。函数的结果是r (Just 'a')
,整个表达式的结果当前是'a' : r (Just 'a')
。再次替换r
:
'a' : f 'b' (const []) (Just 'a')
f
的新参数:
x = 'b'
r = const []
a@(Just q) matches Just 'a'
guard 'b' == 'a' is false
_ matches Just 'a'
我们再次选择第二个选项,因为后卫失败了。警卫基本上说,&#34;如果前一个字符(q
,a
是Nothing
,如果没有前一个字符)与当前字符{x
相同),不要把它放在输出中,否则做&#34;。
因此函数结果为'b' : const [] (Just 'b')
,我们目前看到的完整表达式结果为'a' : 'b' : const [] (Just 'b')
。
const
函数忽略其第二个参数并返回第一个参数。它看起来像这样:
const :: a -> b -> a
const x _ = x
在我们表达式的调用中,参数匹配如下:
x = []
_ = Just 'b'
因此结果为[]
。整个表达式现在是'a' : 'b' : []
,或者是字符串语法"ab"
。已删除重复的'a'
。
如果你愿意的话,你可以在纸上玩这个游戏以获得更长的表达。
该解决方案基本上使用foldr
来构建一个使用延续传递样式的函数,以便它可以携带状态。这是一种时髦的技巧。通过折叠携带状态的更直观的方法是使折叠结果成为实际累加器的元组和您想要携带的状态,然后在最后提取正确的结果。对于您的问题,这可能如下所示:
compress s = fst $ foldr f ([], Nothing) s
where
f cur (acc, (Just prev)) | cur == prev = (acc, (Just prev))
f cur (acc, _) = (cur : acc, Just cur)
这应该也适用。但请注意,此反转状态的顺序,即它向后穿过字符串。我不确定一个人与另一个人的表现或懒惰是什么。我认为CPS解决方案更懒惰,但我不确定。