如何在Haskell中使用foldr实现delete

如何在Haskell中使用foldr实现delete,haskell,fold,Haskell,Fold,过去几天我一直在研究褶皱。我可以用它们实现简单的功能,比如length、concat和filter。我一直在尝试使用foldr函数来实现,比如delete、take和find。我已经用显式递归实现了这些函数,但如何将这些类型的函数转换为右折叠对我来说似乎并不明显 我学习过格雷厄姆·赫顿和伯尼·波普的教程。模仿Hutton的dropWhile,我能够用foldr实现delete,但在无限列表中失败 通过阅读和,我似乎需要使用foldr来生成一个函数,然后执行一些操作。但我并不真正理解这些解决方案,

过去几天我一直在研究褶皱。我可以用它们实现简单的功能,比如
length
concat
filter
。我一直在尝试使用
foldr
函数来实现,比如
delete
take
find
。我已经用显式递归实现了这些函数,但如何将这些类型的函数转换为右折叠对我来说似乎并不明显

我学习过格雷厄姆·赫顿和伯尼·波普的教程。模仿Hutton的dropWhile,我能够用
foldr
实现
delete
,但在无限列表中失败

通过阅读和,我似乎需要使用
foldr
来生成一个函数,然后执行一些操作。但我并不真正理解这些解决方案,也不知道如何以这种方式实现,例如
delete

您能否向我解释一下使用
foldr
惰性版本的函数实现的一般策略,如我提到的那些函数。也许您还可以实现
delete
,作为示例,因为这可能是最简单的方法之一

我想找一个初学者能理解的详细解释。我对解决方案不感兴趣,我想发展一种理解,这样我就能自己想出类似问题的解决方案

谢谢


编辑:在写这篇文章的时候,有一个有用的答案,但不是我想要的。我更感兴趣的是一种使用foldr生成函数的方法,该函数可以执行某些操作。我问题中的链接有这样的例子。我不太了解这些解决方案,因此我想了解更多关于这种方法的信息。

delete
无法在整个列表中均匀运行。计算的结构不仅仅是一次只考虑一个元素的整个列表。它在命中它要查找的元素后会有所不同。这说明它不能仅作为
foldr
实现。必须进行某种后处理

当这种情况发生时,通常的模式是您构建一对值,并在
foldr
完成时只获取其中一个值。这可能就是你模仿Hutton的dropWhile时所做的,尽管我不确定,因为你没有包含代码。像这样的

delete :: Eq a => a -> [a] -> [a]
delete a = snd . foldr (\x (xs1, xs2) -> if x == a then (x:xs1, xs1) else (x:xs1, x:xs2)) ([], [])
主要思想是
xs1
始终是列表的完整尾部,而
xs2
是列表尾部的
delete
的结果。因为您只想删除匹配的第一个元素,所以当您确实匹配您正在搜索的值时,您不想在尾部使用
delete
的结果,您只想返回列表的其余部分不变-幸运的是
xs1
中总是会出现这种情况

是的,这在无限列表上不起作用——但只有一个非常具体的原因。lambda太严格了
foldr
仅当提供的函数不总是强制计算其第二个参数,并且lambda总是强制计算其在对的模式匹配中的第二个参数时,才在无限列表上工作。切换到无可辩驳的模式匹配修复了这一问题,通过允许lambda在检查其第二个参数之前生成构造函数

delete :: Eq a => a -> [a] -> [a]
delete a = snd . foldr (\x ~(xs1, xs2) -> if x == a then (x:xs1, xs1) else (x:xs1, x:xs2)) ([], [])
这并不是唯一的办法。使用let绑定或
fst
snd
作为元组的访问器也可以完成这项工作。但这是差异最小的变化

这里最重要的一点是在处理传递给
foldr
的reduce函数的第二个参数时要非常小心。您希望尽可能推迟检查第二个参数,以便
foldr
可以在尽可能多的情况下延迟流式处理

如果您查看lambda,您会看到在使用reduce函数的第二个参数执行任何操作之前,选择了执行的分支。此外,您将看到,大多数情况下,reduce函数在需要计算第二个参数之前,会在结果元组的两半中生成一个列表构造函数。由于这些列表构造函数是从
delete
中产生的,所以它们对于流媒体来说很重要——只要你不让这两个构造函数碍事。而让这双鞋上的图案匹配无可辩驳是让它不碍事的原因

作为<代码> FurDR 的流属性的一个额外例子,请考虑我最喜欢的例子:

dropWhileEnd :: (a -> Bool) -> [a] -> [a]
dropWhileEnd p = foldr (\x xs -> if p x && null xs then [] else x:xs) []

它流-尽可能多。如果您准确地知道它何时流、为什么流、为什么不流,您将了解
foldr

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
的确切含义在这里有点棘手。它实际上是处理列表其余部分的函数。事实上,把它看作是一个延续是准确的。它绝对代表执行剩余的折叠,wi
{-# 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
{-# 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
{-# 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
{-# 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
{-# 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
delete :: (Eq a) => a -> [a] -> [a]
delete a xs = foldr (\x xs -> if x == a then (xs) else (x:xs)) [] xs