我正在尝试使用filter
来制作foldr
。我有一个解决方案,但不知道为什么我的版本不起作用。
foldr
的工作方式如下:
>>>foldr op z (xs:x)
x op z
然后重复,对吗?当x是列表中的最后一个元素时。
现在,我有
myFilter pred = foldr op z
where
z = []
op x xs = if pred x then x
这不起作用,给出了parse error (possibly incorrect indentation or mismatched brackets)
错误。
该操作仅将x
赋予pred
,如果为true
,则返回x
,否则将其跳过。 foldr
的工作方式是在所传递的xs
列表上不断循环。那为什么我需要做
op x xs = if pred x then x : xs else xs
并告诉它继续xs
,即使foldr
已经继续执行了吗?
答案 0 :(得分:5)
foldr :: (a -> b -> b) -> b -> [a] -> b
遍历a
的输入列表,并操纵b
(代表循环的状态)。 op
的工作是从输入列表中获取a
,即当前状态b
,然后计算一个新状态。 foldr
确保列表中的每个项目都调用op
。
对于您的filter
,b
也是a
的列表。因此,您的代码正在讨论a
的两个不同列表-输入列表和输出列表。您的op
必须决定我是否应该将此x
放在输出列表中?
为澄清起见,这是您的代码,其中的变量命名更具暗示性。
filter :: (a -> Bool) -> [a] -> [a]
filter pred = foldr addToOutputIfNecessary initialOutput
where
initialOutput = []
addToOuputIfNecessary itemFromInput currentOutput =
if pred itemFromInput
then itemFromInput:currentOutput
else currentOutput
因此,当我们从currentOutput
返回addToOutputIfNecessary
时,并不是要告诉foldr
继续。 (foldr
本身将始终继续到下一个输入元素-您无需告诉它继续。)这只是说循环的状态并没有改变此迭代;我们决定不向输出列表添加项目。
希望这会有所帮助!如果您不了解任何内容,请在评论中告诉我。
答案 1 :(得分:5)
foldr
封装了将列表中的元素组合在一起以及折叠其余列表的结果所涉及的递归。 xs
不是您输入内容的结尾;这是折叠输入尾部的结果。
您可以通过重点关注op
要执行的操作来强调这一点,并通过重构
op x xs = if pred x then x : xs else xs
到
-- (x:) xs == x:xs
-- id xs == xs
op x xs = (if pred x then (x:) else id) xs
可以被eta化为
op x = if pred x then (x:) else id
换句话说,给定列表的第一个元素,您将如何处理:将x
附加到递归结果中,还是按原样返回该结果?
答案 2 :(得分:3)
让我们看看列表的foldr
的类型:
foldr :: (a -> b -> b) -> b -> [a] -> b
现在您正在像这样使用它:
foldr op z
从foldr
的类型签名中可以看到,函数op
必须返回类型为b
的东西,该类型与z
相同。由于您执行z = []
,因此它必须是一个列表。因此op x xs
的结果必须是列表。
此外,请记住,Haskell中的if...else
是表达式。这意味着无论条件是对还是错,它都必须求值到某个值。在其他语言中,else
是可选的,因为if
是声明。在Haskell中,else
不是可选的。
那我为什么需要做
op x xs = if pred x then x : xs else xs
并告诉它继续使用xs,即使foldr已经执行了该操作?
一个原因是op
必须始终返回一个值。 foldr
将使用此值继续进行计算。没有它,foldr
将无法继续,实际上您的代码甚至无法如您所见进行编译。