delete 是一种模态搜索。它有两种不同的操作模式——无论是否已经找到结果。您可以使用foldr 构造一个函数,在检查每个元素时将状态向下传递。所以在delete的情况下,状态可以是一个简单的Bool。这不是最好的类型,但可以。
一旦您确定了状态类型,您就可以开始处理foldr 构造。我将按照我的方式逐步解决。我将启用ScopedTypeVariables,以便更好地注释子表达式的类型。如果您知道状态类型,就知道您希望foldr 生成一个函数,该函数采用该类型的值,并返回所需的最终类型的值。这足以开始绘制草图了。
{-# LANGUAGE ScopedTypeVariables #-}
delete :: forall a. Eq a => a -> [a] -> [a]
delete a xs = foldr f undefined xs undefined
where
f :: a -> (Bool -> [a]) -> (Bool -> [a])
f x g = undefined
这是一个开始。 g 的确切含义在这里有点棘手。它实际上是处理列表其余部分的函数。事实上,将其视为延续是准确的。它绝对代表以您选择传递的任何状态执行其余的折叠。鉴于此,是时候弄清楚在其中一些 undefined 位置放置什么了。
{-# LANGUAGE ScopedTypeVariables #-}
delete :: forall a. Eq a => a -> [a] -> [a]
delete a xs = foldr f undefined xs undefined
where
f :: a -> (Bool -> [a]) -> (Bool -> [a])
f x g found | x == a && not found = g True
| otherwise = x : g found
这似乎相对简单。如果当前元素是正在搜索的元素,但尚未找到,则不输出,继续状态设置为True,表示已找到。 otherwise,输出当前值,继续当前状态。这只是将其余参数留给foldr。最后一个是初始状态。另一个是空列表的状态函数。好的,这些也不错。
{-# LANGUAGE ScopedTypeVariables #-}
delete :: forall a. Eq a => a -> [a] -> [a]
delete a xs = foldr f (const []) xs False
where
f :: a -> (Bool -> [a]) -> (Bool -> [a])
f x g found | x == a && not found = g True
| otherwise = x : g found
无论状态如何,遇到空列表时产生一个空列表。并且初始状态是尚未找到要搜索的元素。
这种技术也适用于其他情况。例如,foldl 可以这样写成foldr。如果您将foldl 视为重复转换初始累加器的函数,您可以猜到这就是正在生成的函数 - 如何转换初始值。
{-# LANGUAGE ScopedTypeVariables #-}
foldl :: forall a b. (a -> b -> a) -> a -> [b] -> a
foldl f z xs = foldr g id xs z
where
g :: b -> (a -> a) -> (a -> a)
g x cont acc = undefined
当问题被定义为操纵初始累加器时,基本情况并不难找到,在那里命名为z。空列表为恒等变换id,传递给创建函数的值为z。
g 的实现比较复杂。它不能只是盲目地对类型进行,因为有两种不同的实现使用所有预期值和类型检查。这是类型不够用的情况,需要考虑可用函数的含义。
让我们从看起来应该使用的值及其类型的清单开始。 g 的正文中似乎必须使用的东西是f :: a -> b -> a、x :: b、cont :: (a -> a) 和acc :: a。 f 显然会将x 作为它的第二个参数,但是有一个问题是在适当的地方使用cont。要弄清楚它的去向,请记住它表示处理列表的其余部分返回的转换函数,foldl 处理当前元素,然后将该处理的结果传递给列表的其余部分。
{-# LANGUAGE ScopedTypeVariables #-}
foldl :: forall a b. (a -> b -> a) -> a -> [b] -> a
foldl f z xs = foldr g id xs z
where
g :: b -> (a -> a) -> (a -> a)
g x cont acc = cont $ f acc x
这也表明foldl' 可以这样写,只需稍作改动:
{-# LANGUAGE ScopedTypeVariables #-}
foldl' :: forall a b. (a -> b -> a) -> a -> [b] -> a
foldl' f z xs = foldr g id xs z
where
g :: b -> (a -> a) -> (a -> a)
g x cont acc = cont $! f acc x
不同之处在于($!) 用于建议在将f acc x 传递给cont 之前对其进行评估。 (我说“建议”是因为在某些极端情况下,($!) 甚至不会强制评估 WHNF。)