编写此函数的惯用方式是什么(通常可以称为filterA)?

时间:2019-06-02 17:00:55

标签: haskell applicative

我正在上this课程。

Applicative的一部分,要求我实现具有以下行为和类型的函数

-- | Filter a list with a predicate that produces an effect.
--
-- >>> filtering (ExactlyOne . even) (4 :. 5 :. 6 :. Nil)
-- ExactlyOne [4,6]
--
-- >>> filtering (\a -> if a > 13 then Empty else Full (a <= 7)) (4 :. 5 :. 6 :. Nil)
-- Full [4,5,6]
--
-- >>> filtering (\a -> if a > 13 then Empty else Full (a <= 7)) (4 :. 5 :. 6 :. 7 :. 8 :. 9 :. Nil)
-- Full [4,5,6,7]
--
-- >>> filtering (\a -> if a > 13 then Empty else Full (a <= 7)) (4 :. 5 :. 6 :. 13 :. 14 :. Nil)
-- Empty
--
-- >>> filtering (>) (4 :. 5 :. 6 :. 7 :. 8 :. 9 :. 10 :. 11 :. 12 :. Nil) 8
-- [9,10,11,12]
--
-- >>> filtering (const $ True :. True :.  Nil) (1 :. 2 :. 3 :. Nil)
-- [[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3]]
filtering :: Applicative f => (a -> f Bool) -> List a -> f (List a)

我想出了以下实现,可以满足所有要求

filtering f as =
  let x = sequence (f `map` as)
      y = zip as <$> x
      z = filter snd <$> y
   in map fst <$> z

但是对我来说感觉有点“四处走动”,我想不出更直接的方法来做到这一点。

注意:我已经扩展到x, y, z,因为它(对于我来说)使跟踪事情变得更加容易,尽管我意识到我可以全部用一行来表达,但我不认为这是更“直接”,因此无法回答我的问题。

注2:这门课程似乎是从基础知识构建通用类型类。我们从List的自定义实现开始,之后是Functor,现在是Applicative,因此我只能使用这些类中的概念。我还不能使用Monad中的任何东西。

2 个答案:

答案 0 :(得分:4)

我的第一个想法是从简单的filter开始:

filter :: (a -> Bool) -> List a -> List a
filter _ Nil = Nil
filter f (x :. xs) =
    let b = f x
        ys = filter f xs
    in
    if b then x :. ys else ys

...并尝试将其扩展到Applicative

filtering :: (Applicative f) => (a -> f Bool) -> List a -> f (List a)
filtering _ Nil = pure Nil
filtering f (x :. xs) =
    let b = f x
        ys = filtering f xs
    in
    if b then x :. ys else ys

此尝试有两个问题:f xf Bool,而不是Bool,因此if b then ...是类型错误,而filtering f xsf (List a),而不是List a,因此x :. ys是类型错误。

我们可以使用lift2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c来解决这些问题:

filtering f (x :. xs) =
    lift2 (\b ys -> if b then x :. ys else ys) (f x) (filtering f xs)

lift2让我们分别从BoolList a本地提取f xfiltering f xs;或更正确地说,我们将if ... then ... else计算包装在一个函数中,然后lift2推送到f中。

或者,我们可以直接使用<$><*>

filtering f (x :. xs) =
    (\b ys -> if b then x :. ys else ys) <$> f x <*> filtering f xs

或编写我们的帮助程序功能稍有不同:

filtering f (x :. xs) =
    (\b -> if b then (x :.) else id) <$> f x <*> filtering f xs

答案 1 :(得分:2)

这里是根据foldr的实现(并使用 base 类型和函数编写)。我相当确定它等同于melpomene's solution

import Control.Applicative (liftA2)
import Data.Bool (bool)

filterA :: Applicative f => (a -> f Bool) -> [a] -> f [a]
filterA f = foldr (\x xs -> liftA2 (++) (bool [] [x] <$> f x) xs) (pure [])

一些值得注意的细节:

  • bool y x bif b then x else y的无点友好语。

  • 使用(++)代替(:)添加元素很好,因为我们将其添加到列表的最前面。

  • xs实际上不是列表,它的类型为f [a]

演示:

GHCi> filterA (\x -> print x *> pure (x > 5)) [1..10]
1
2
3
4
5
6
7
8
9
10
[6,7,8,9,10]

这是受您原始解决方案启发的另一种做法(请注意sequence (map f xs)traverse f xs相同):

filterA :: Applicative f => (a -> f Bool) -> [a] -> f [a]
filterA f = fmap concat . traverse (\x -> bool [] [x] <$> f x)

(也可以使用bool Nothing (Just x)中的{catMaybesData.Maybe,而不是bool [] [x]concat。)

请注意,此解决方案还需要遍历列表,因为traverse不足以实现过滤。这就是为什么filtercatMaybesfilterA和朋友需要different classes才能被概括的原因。