微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在Haskell中结合多种过滤功能的优雅方法

如何解决在Haskell中结合多种过滤功能的优雅方法

将以下过滤功能用作一元谓词,

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

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

f3 :: Int -> Bool
f3 x = x `mod` 3 == 0

我想过滤所有整数列表。目前,我正在按照以下方式进行操作:

filtered = filter f1 $ filter f2 $ filter f3 [1..90]
-- [33,36,39,42,45,48,51,54,57]

,但是几乎没有感觉这是最优雅的解决方案。尤其是我不喜欢filter的多次重复以及缺乏可组合性。

是否有一种方法可以将所有这些谓词组合为一个,我们将其命名为<?>,以便可能的语法类似于以下内容

filtered = filter (f1 <?> f2 <?> f3) [1..90]
-- [33,57]

这个假设的<?>运算符的类型签名在Hoogle上将是(a -> Bool) -> (a -> Bool) -> (a -> Bool)I wasn't able to find any such thing

解决方法

那呢?

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

在这里,将&&提升到Applicative会得到您标记为<?>的内容,即,一个运算符将一起结果一元谓词中的一个。

((起初,我为提升运算符使用名称.&&.,但是amalloy建议<&&>与其他{{1} } / Functor取消了Applicative 之类的运算符。)

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

基本上,上述方法是有效的,因为sequence(在上面使用的(->) a monad上)获取功能列表并返回一个功能返回列表。例如

sequence [f,g,h] = \x -> [f x,g x,h x]

使用and :: [Bool] -> Bool进行后期组合可以得到一个布尔值,因此可以在filter中使用它。

此外,成为有意义的人不会感到羞耻:

> filter (\x -> f1 x && f2 x && f3 x) [1..100]

仅稍长一些,并且可以说更易于阅读。

,

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

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

filtered = filter (f1 &&^ f2 &&^ f3) [1..90]

这给了我们

Prelude Control.Monad.Extra> filter (f1 &&^ f2 &&^ f3) [1..90]
[33,57]

(&&^)函数为implemented as [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)

这行得通,因为函数类型是Monad

instance Monad ((->) r) where
    f >>= k = \ r -> k (f r) r

因此,这意味着ifM与以下功能一样实现:

-- ifM for ((->) r)
ifM b t f x
    | b x = t x
    | otherwise = f x

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

,

我们需要一种使用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,f3] )  [1..90]
 [33,57]
 λ> 

检查:

 λ> 
 λ> filter  (\x -> and (map ($ x) [f1,f3]))  [1..90]
 [33,57]
 λ> 
,

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

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]

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

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

filtered = filterP (p1 <> p2 <> p3) [1..90]

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

filtered = filter (getPredicate (mconcat [p1,p2,p3]))

您可以再次将其重构为类似的内容

filterByAll = filter . getPredicate . mconcat

filtered = filterByAll [p1,p3] [1..90]
,

其他答案都很好,但是我将给出我喜欢的组合函数的方式,这非常紧凑。 我非常喜欢使用Control.Monad的提升功能

filter $ liftM2 (&&) f1 f2

liftM2通过将(&&)函数提升为monad并以f1和f2作为参数来工作。

我知道有一个名为liftM3的函数,但是我不确定它是否可以在这种情况下工作。

https://laravel-livewire.com/docs/2.x/installation#publish-assets

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。