我正在上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
中的任何东西。
答案 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 x
是f Bool
,而不是Bool
,因此if b then ...
是类型错误,而filtering f xs
是f (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
让我们分别从Bool
和List a
本地提取f x
和filtering 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 b
是if 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)
中的{catMaybes
和Data.Maybe
,而不是bool [] [x]
和concat
。)
请注意,此解决方案还需要遍历列表,因为traverse
不足以实现过滤。这就是为什么filter
,catMaybes
,filterA
和朋友需要different classes才能被概括的原因。