在 Haskell 中结合多个过滤功能的优雅方式

Jiv*_*van 32 haskell filter function-composition

给定以下过滤函数作为一元谓词,

f1 :: Int -> Bool
f1 x = x > 30

f2 :: Int -> Bool
f2 x = x < 60

f3 :: Int -> Bool
f3 x = x `mod` 3 == 0
Run Code Online (Sandbox Code Playgroud)

我想通过所有这些来过滤整数列表。目前我正在做一些事情:

filtered = filter f1 $ filter f2 $ filter f3 [1..90]
-- [33,36,39,42,45,48,51,54,57]
Run Code Online (Sandbox Code Playgroud)

但很难说这是最优雅的解决方案;特别是我不喜欢 的多次重复filter和缺乏可组合性。

有没有办法将所有这些谓词组合成一个,让我们命名它<?>,以便可能的语法类似于以下内容?

filtered = filter (f1 <?> f2 <?> f3) [1..90]
-- [33,36,39,42,45,48,51,54,57]
Run Code Online (Sandbox Code Playgroud)

这个假设<?>运算符的类型签名将是,(a -> Bool) -> (a -> Bool) -> (a -> Bool)我无法在 Hoogle 上找到任何这样的东西

Enr*_*lis 34

那这个呢?

import Control.Applicative (liftA2)
-- given f1 etc.
filtered = filter (f1 <&&> f2 <&&> f3) [1..90]
  where
    (<&&>) = liftA2 (&&)
Run Code Online (Sandbox Code Playgroud)

在这里,提升&&toApplicative给出了您标记为 的内容<?>,即运算符 to并将两个一元谓词的结果放在一起。

(我最初使用了.&&.提升操作符的名称,但amalloy建议通过类比其他/提升操作符(如<&&>将是一个更好的名称。)FunctorApplicative<$>


chi*_*chi 22

> filter (and . sequence [f1, f2, f3]) [1..100]
[33,36,39,42,45,48,51,54,57]
Run Code Online (Sandbox Code Playgroud)

基本上上述工作是因为sequence(在(->) a上面使用的monad 上)接受一个函数列表并返回一个函数返回列表。例如

sequence [f, g, h] = \x -> [f x, g x, h x]
Run Code Online (Sandbox Code Playgroud)

后合成and :: [Bool] -> Bool为您提供一个布尔结果,因此您可以在filter.

此外,有针对性也没有什么可耻的:

> filter (\x -> f1 x && f2 x && f3 x) [1..100]
Run Code Online (Sandbox Code Playgroud)

只是稍微长一点,而且可以说更容易阅读。


Wil*_*sem 9

您可以使用工作(&&^) :: Monad m => m Bool -> m Bool -> m Bool中的extra

import Control.Monad.Extra((&&^))

filtered = filter (f1 &&^ f2 &&^ f3) [1..90]
Run Code Online (Sandbox Code Playgroud)

这给了我们:

Prelude Control.Monad.Extra> filter (f1 &&^ f2 &&^ f3) [1..90]
[33,36,39,42,45,48,51,54,57]
Run Code Online (Sandbox Code Playgroud)

(&&^)函数实现为 [src]

ifM :: Monad m => m Bool -> m a -> m a -> m a
ifM b t f = do b <- b; if b then t else f

-- …

(&&^) :: Monad m => m Bool -> m Bool -> m Bool
(&&^) a b = ifM a b (pure False)
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为函数类型是Monad

instance Monad ((->) r) where
    f >>= k = \ r -> k (f r) r
Run Code Online (Sandbox Code Playgroud)

因此,这意味着ifM作为函数实现的 是:

-- ifM for ((->) r)
ifM b t f x
    | b x = t x
    | otherwise = f x
Run Code Online (Sandbox Code Playgroud)

(&&^)因此,该函数检查第一个条件b x是否为True,如果不是,它将返回False(因为fconst Falsef x因此是False)。如果b xis True,它将检查链中的下一个元素。


jpm*_*ier 6

我们需要一种方法来使用函数and来组合谓词,而不仅仅是布尔值。

一种懒惰的方法是向Hoogle询问类型签名,例如Functor f => ([b]-> b) -> [f b] -> f b,其中 f 大概是类似Int ->. 满足库函数cotraverse

它似乎工作正常:

 ?> 
 ?> f1 x = x > 30
 ?> f2 x = x < 60
 ?> f3 x = (mod x 3) == 0
 ?> 
 ?> import Data.Distributive (cotraverse)
 ?> :t cotraverse
 cotraverse
  :: (Distributive g, Functor f) => (f a -> b) -> f (g a) -> g b
 ?> 
 ?> filter  ( cotraverse and [f1,f2,f3] )  [1..90]
 [33,36,39,42,45,48,51,54,57]
 ?> 

Run Code Online (Sandbox Code Playgroud)

检查:

 ?> 
 ?> filter  (\x -> and (map ($ x) [f1,f2,f3]))  [1..90]
 [33,36,39,42,45,48,51,54,57]
 ?> 
Run Code Online (Sandbox Code Playgroud)


che*_*ner 5

Data.Monoid定义Predicate可用于表示您的函数的类型:

import Data.Monoid

-- newtype Predicate t = Predicate { getPredicate :: t -> Bool }
p1 :: Predicate Int
p1 x = Predicate $ x > 30

p2 :: Predicate Int
p2 x = Predicate $ x < 60

p3 :: Predicate Int
p3 x = Predicate $ x `mod` 3 == 0
Run Code Online (Sandbox Code Playgroud)

Predicate有一个Semigroup将两个谓词组合成一个的实例,如果两个输入谓词都满足,则满足。

-- instance Semigroup (Predicate a) where
-- Predicate p <> Predicate q = Predicate $ \a -> p a && q a

filtered = filter (getPredicate (p1 <> p2 <> p3)) [1..90]
Run Code Online (Sandbox Code Playgroud)

不幸的是,您需要先解开组合的谓词,然后才能将它们与filter. 您可以定义自己的filterP函数并使用它代替filter

filterP :: Predicate t  -> [t] -> [t]
filterP = filter . getPredicate

filtered = filterP (p1 <> p2 <> p3) [1..90]
Run Code Online (Sandbox Code Playgroud)

还有一个Monoid实例(身份是一个总是返回的谓词True),你可以像这样使用

filtered = filter (getPredicate (mconcat [p1, p2, p3]))
Run Code Online (Sandbox Code Playgroud)

您可以再次将其重新考虑为

filterByAll = filter . getPredicate . mconcat

filtered = filterByAll [p1, p2, p3] [1..90]
Run Code Online (Sandbox Code Playgroud)