如果我们真的想的话,可以使用foldr编写filterFirst,因为foldr是一种"通用"的函数 - 它允许我们使用递归执行任何列表转换。主要缺点是生成的代码相当令人费解。在我看来,在这种情况下,显式递归要好得多。
无论如何,以下是操作步骤。这依赖于我认为是反模式的东西,即“向foldr传递四个参数”。我将其称为反模式,因为foldr通常仅使用三个参数调用,并且结果不是接受第四个参数的函数。
filterFirst :: (a->Bool)->[a]->[a]
filterFirst p xs = foldr go (\_ -> []) xs True
where
go y ys True
| p y = y : ys True
| otherwise = ys False
go y ys False = y : ys False
清楚吗?不是很清楚。这里的诀窍是利用foldr
构建一个函数Bool -> [a]
,如果使用False
调用,则返回原始列表;如果使用True
调用,则返回过滤后的第一个列表。如果我们使用以下方法来创建该函数:
foldr go baseCase xs
最初的回答是:
结果显然如下
。
foldr go baseCase xs True
现在,基本情况必须处理空列表,在这种情况下,我们必须返回一个返回空列表的函数,无论布尔参数是什么。因此,我们得到
def filter_list(lst, f):
if not lst:
return lambda _: []
"最初的回答"
foldr go (\_ -> []) xs True
现在,我们需要定义
go
函数。它的参数包括:
- 一个列表元素
y
- "递归" 的结果
ys
(对于列表的其余部分是一个 Bool->[a]
函数)
go
必须返回一个 Bool->[a]
函数来处理更大的列表。所以让我们考虑:
- 布尔类型的参数
最后,让 go
返回一个列表。如果布尔值为 False
,我们必须返回原始列表,因此:
最初的回答:
现在,我们需要定义函数 `go`。它的参数包括:
1. 一个列表元素 `y`
2. “递归” 的结果 `ys`(对于列表的其余部分是一个 `Bool->[a]` 函数)
3. 一个布尔类型的参数
`go` 必须返回一个 `Bool->[a]` 函数,用于处理更大的列表。因此,如果布尔值为 `False`,我们必须返回原始列表,例如:
go y ys False = y : ys False
请注意,
ys False
意味着“尾部未更改”,因此我们实际上是在重建整个列表而没有做任何修改。
如果布尔值为 true,则像
p y
一样查询谓词。如果为 false,则丢弃
y
并返回列表尾部不变。
go y ys True
| p y = -- TODO
| otherwise = ys False
如果
p y
为真,我们保留
y
并返回过滤后的列表尾部。最初的回答。
go y ys True
| p y = y : ys True
| otherwise = ys False
作为最终说明,我们可以使用一对
([a], [a])
而不是函数
Bool -> [a]
,但这种方法在处理更复杂的情况时不太适用。
所以,这就是全部内容。这种技巧很有用,但我不建议在真正需要让别人理解的代码中使用它。
deleteBy
函数的用法:deleteBy (>) 1 [5,-6,-7,9,10]
- melpomenefoldr
编写filterFirst
(和其他任何东西),但这样做会相当不优雅。使用mapAccumL
可能会提供更好的结果,但在我看来,显式递归是最好的选择。 - chideleteBy (>=)
,我认为...这说明了为什么将非对称函数传递给deleteBy
是我要谨慎的事情。 - user11228628Maybe a
的东西,然后取出Just
)。如果有一个非conduit模拟的话,像Data.Conduit.List中的concatMapAccum
可以做到这一点(虽然更通用,可以将1个元素发送到任意数量的结果)。 - moonGoosedeleteBy
将非常适合。实际上,有人呼吁将deleteBy
更改为(a -> Bool) -> [a] -> [a]
...但是,除了否定谓词之外,你最终会得到filterFirst
,正如OP在这里展示的那样。 - duplode