在 Haskell 中组合多个过滤函数的优雅方式
Elegant way to combine multiple filtering functions in 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,36,39,42,45,48,51,54,57]
这个假设的 <?>
运算符的类型签名将是 (a -> Bool) -> (a -> Bool) -> (a -> Bool)
但 Hoogle 上的 I wasn't able to find any such thing。
您可以使用 (&&^) :: Monad m => m Bool -> m Bool -> m Bool
of the extra
package:
import Control.Monad.Extra((&&^))
filtered = filter (f1 <b>&&^</b> f2 <b>&&^</b> f3) [1..90]
这给了我们:
Prelude Control.Monad.Extra> filter (f1 &&^ f2 &&^ f3) [1..90]
[33,36,39,42,45,48,51,54,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
,如果不是,它将 return False
(因为f
是 const False
,因此 f x
是 False
)。如果 b x
是 True
,它将检查链中的下一个元素。
> filter (and . sequence [f1, f2, f3]) [1..100]
[33,36,39,42,45,48,51,54,57]
基本上上面的工作是因为 sequence
(在上面使用的 (->) a
monad 上)采用 list-of-functions 和 returns 和 function-returning-a-list。例如
sequence [f, g, h] = \x -> [f x, g x, h x]
Post-composing 和 and :: [Bool] -> Bool
给你一个布尔结果,所以你可以在 filter
.
中使用它
此外,成为 point-ful:
并不丢脸
> filter (\x -> f1 x && f2 x && f3 x) [1..100]
只是稍长一些,而且可以说更易于阅读。
这个呢?
import Control.Applicative (liftA2)
-- given f1 etc.
filtered = filter (f1 <&&> f2 <&&> f3) [1..90]
where
(<&&>) = liftA2 (&&)
在这里,将 &&
提升到 Applicative
会得到您标记为 <?>
的内容,即 和 的运算符 两个一元谓词的结果。
我最初使用名称 .&&.
作为提升运算符,但 amalloy 建议 <&&>
是一个更好的名称 通过类比其他 Functor
/Applicative
提升运算符,如 <$>
.
其他答案都不错,但我会给出我喜欢的组合函数的方式,非常紧凑。
我非常喜欢使用 Control.Monad
中的提升函数
filter $ liftM2 (&&) f1 f2
liftM2 通过将 (&&) 函数提升为 monad 并将 f1 和 f2 作为参数来工作。
我知道有一个名为 liftM3 的函数,但我不确定它是否适用于这种情况。
https://hackage.haskell.org/package/base-4.14.0.0/docs/Control-Monad.html#v:liftM3
我们需要一种方法来使用像 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]
λ>
检查中:
λ>
λ> filter (\x -> and (map ($ x) [f1,f2,f3])) [1..90]
[33,36,39,42,45,48,51,54,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
实例,它将两个谓词组合成一个,如果 both 输入谓词都满足。
-- 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
实例(身份是一个始终 returns True
的谓词),您可以像
filtered = filter (getPredicate (mconcat [p1, p2, p3]))
你又可以 re-factor 类似
filterByAll = filter . getPredicate . mconcat
filtered = filterByAll [p1, p2, p3] [1..90]
给定以下过滤函数作为一元谓词,
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,36,39,42,45,48,51,54,57]
这个假设的 <?>
运算符的类型签名将是 (a -> Bool) -> (a -> Bool) -> (a -> Bool)
但 Hoogle 上的 I wasn't able to find any such thing。
您可以使用 (&&^) :: Monad m => m Bool -> m Bool -> m Bool
of the extra
package:
import Control.Monad.Extra((&&^))
filtered = filter (f1 <b>&&^</b> f2 <b>&&^</b> f3) [1..90]
这给了我们:
Prelude Control.Monad.Extra> filter (f1 &&^ f2 &&^ f3) [1..90]
[33,36,39,42,45,48,51,54,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
,如果不是,它将 return False
(因为f
是 const False
,因此 f x
是 False
)。如果 b x
是 True
,它将检查链中的下一个元素。
> filter (and . sequence [f1, f2, f3]) [1..100]
[33,36,39,42,45,48,51,54,57]
基本上上面的工作是因为 sequence
(在上面使用的 (->) a
monad 上)采用 list-of-functions 和 returns 和 function-returning-a-list。例如
sequence [f, g, h] = \x -> [f x, g x, h x]
Post-composing 和 and :: [Bool] -> Bool
给你一个布尔结果,所以你可以在 filter
.
此外,成为 point-ful:
并不丢脸> filter (\x -> f1 x && f2 x && f3 x) [1..100]
只是稍长一些,而且可以说更易于阅读。
这个呢?
import Control.Applicative (liftA2)
-- given f1 etc.
filtered = filter (f1 <&&> f2 <&&> f3) [1..90]
where
(<&&>) = liftA2 (&&)
在这里,将 &&
提升到 Applicative
会得到您标记为 <?>
的内容,即 和 的运算符 两个一元谓词的结果。
我最初使用名称 .&&.
作为提升运算符,但 amalloy 建议 <&&>
是一个更好的名称 通过类比其他 Functor
/Applicative
提升运算符,如 <$>
.
其他答案都不错,但我会给出我喜欢的组合函数的方式,非常紧凑。 我非常喜欢使用 Control.Monad
中的提升函数filter $ liftM2 (&&) f1 f2
liftM2 通过将 (&&) 函数提升为 monad 并将 f1 和 f2 作为参数来工作。
我知道有一个名为 liftM3 的函数,但我不确定它是否适用于这种情况。
https://hackage.haskell.org/package/base-4.14.0.0/docs/Control-Monad.html#v:liftM3
我们需要一种方法来使用像 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]
λ>
检查中:
λ>
λ> filter (\x -> and (map ($ x) [f1,f2,f3])) [1..90]
[33,36,39,42,45,48,51,54,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
实例,它将两个谓词组合成一个,如果 both 输入谓词都满足。
-- 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
实例(身份是一个始终 returns True
的谓词),您可以像
filtered = filter (getPredicate (mconcat [p1, p2, p3]))
你又可以 re-factor 类似
filterByAll = filter . getPredicate . mconcat
filtered = filterByAll [p1, p2, p3] [1..90]