Haskell 过滤应用程序

Haskell 过滤应用程序,haskell,Haskell,我正在学习NICTA Haskell课程,坚持学习应用程序的最后一部分,以下是我的学习进度 基本上,我们要用谓词过滤列表,该谓词在应用程序上下文中生成列表 filtering :: Applicative f => (a -> f Bool) -> List a -> f (List a) filtering _ Nil = pure Nil filtering f (x :. rest) = let whole = lift2 (:.) (pure x

我正在学习NICTA Haskell课程,坚持学习应用程序的最后一部分,以下是我的学习进度

基本上,我们要用谓词过滤列表,该谓词在应用程序上下文中生成列表

filtering :: Applicative f => (a -> f Bool) -> List a -> f (List a)
filtering _ Nil = pure Nil
filtering f (x :. rest) = 
        let whole = lift2 (:.) (pure x) (filtering f rest) in
        lift2 filter (const <$> f x) whole
filtering::Applicative f=>(a->f Bool)->列表a->f(列表a)
过滤Nil=纯零
过滤f(x:.剩余)=
让整个=提升2(:)(纯x)(过滤f剩余)英寸
lift2滤波器(常数f x)整体
所以
total
这里是完整的未过滤列表,所以我提升了一个
filter
,在
(const f x)
中添加的常量是为了满足
filter
中的
(a->Bool)
要求

一切正常,编译成功,但其中一个测试用例失败,所以这里一定出了问题

例如,
filtering(Id.even)[4,5,6]
只返回
Id[4]
,而不是
Id[4,6]
(Id只是一个容器,是一个应用程序。)

我在什么地方出错了吗?

这里的问题是
(const f x)
。该函数将用于检查整个列表,但它只提供当前
x
上的
f
结果。这意味着当它处理
[5,6]
子列表时,它本质上是
过滤器(const False)[5,6]
,这会导致一个空列表

您不能调用
filter
为您执行此操作,因为它的形状不正确。这样的事情总会发生。相反,只需关注是否包含当前元素,并让递归正确处理列表的其余部分。(我尽量不多说,因为这门课的目的当然是让你自己弄清楚。)

这个怎么样(我用的是普通的列表,不是你的ctor)


我对你认为“完整未过滤列表”的必要性感到困惑,我想你也是。完整的未筛选列表将显示它
x:。休息
,你已经有了

显然,你的意思是“如果谓词总是产生
True
,那么整个结果就是这样……但这没有任何意义,因为你基本上预测它总是产生
True

您需要考虑的是,而不是这个代码>整< /Cord>东西,是两个应用性包装的值:谓词在头上的结果——即“代码> fx::f BOOL。并且,作为递归,列表中的过滤其余部分,<代码>过滤F REST::F(列表A)< /代码> ./P> 现在,正如您明显注意到的,

liftA2
通常是组合两个
Applicative
包装的值的最简单方法(尽管
实际上倾向于使代码更整洁)

在这种情况下,

liftA2 :: (Bool->List a->List a) -> f Bool -> f (List a) -> f (List a)

因此,您需要一个函数
Bool->List a->List a
,如果布尔值为真,它会在
x
前面加上前缀,否则列表就会保持原样。好吧,在本地
块中定义它应该不是一个问题。

如果不在一个函数中尝试,而是将它分解成更小的子块,那么这个问题就会简单得多问题

我要给出的第一个提示是,它看起来非常像类的问题,其中包括这个方法,它的签名应该让你去“嗯!“:

通过有趣的
列表
输入您的问题,我推测您正在学习的课程不允许使用库函数来解决问题。我不太同意这种做法;我认为最好的学习方法是以下两步过程:

  • 使用库函数解决问题。通过这种方式,您可以了解库,更重要的是,您可以了解如何将问题分解为更小、更通用的库
  • 编写您自己使用的库函数版本。通过这种方式,您可以了解基于库的解决方案的工作原理
  • 因此,我建议作为子问题,您应该尝试为您的
    列表
    类型编写自己版本的
    遍历

    下一个子问题:使用
    遍历
    ,编写此函数应该很简单:

    tagWithBool :: (a -> f Bool) -> -> List a -> f (List (Bool, a))
    
    -- Remove the items tagged with `False`, and eliminate the tags.
    removeFalse :: List (Bool, a) -> List a
    
    一旦有了它,我要做的下一步就是编写这个函数:

    tagWithBool :: (a -> f Bool) -> -> List a -> f (List (Bool, a))
    
    -- Remove the items tagged with `False`, and eliminate the tags.
    removeFalse :: List (Bool, a) -> List a
    
    同样,我建议您在编写本文时充分利用实用函数,如
    map
    filter
    ,在它正常工作后,编写自己版本的函数作为额外练习。(事实上,考虑到问题中的非标准
    列表
    类型,您几乎不得不编写自己的版本来以这种方式解决问题。)

    一旦你掌握了所有这些,你就可以写:

    filtering :: Applicative f => (a -> f Bool) -> List a -> f (List a)
    filtering p xs = fmap removeFalse (tagWithBool xs)
    

    请注意,这使用了
    fmap
    ,因此您必须为
    List
    类型实现
    Functor
    (或者只是一个
    mapList::(a->b)->List a->List b
    函数).

    Id
    是什么,
    过滤
    序列
    有什么关系?@luqui如果你假设
    序列
    应该是
    过滤
    Id
    是平凡的
    应用程序
    实例的构造函数,那么这个问题是有意义的。为什么你都在列表上递归,同时运行fil这两个操作都访问了整个列表,似乎给了你平方复杂度而不是linearTypo,对不起,伙计们……这应该是filtering@tohava我不知道如何将
    (:)
    更改为
    如果f x那么(:)否则跳过
    或类似的东西,因为Bool是在上下文中的。有什么建议吗?啊!没错。我如何在第一次
    提升中调整
    (:)
    ,以便跳过东西?这里的问题是Bool在context@bluebelle提示:获取可作为
    (:)输入的
    Bool
    更换,也可以将
    (纯x)
    (您不需要,因为您已经有
    x
    作为参数可用)更改为
    (f x)
    。这样就行了
    filtering :: Applicative f => (a -> f Bool) -> List a -> f (List a)
    filtering p xs = fmap removeFalse (tagWithBool xs)