【发布时间】:2010-10-24 22:04:08
【问题描述】:
函数的过滤器类接受一个条件(a -> Bool)并在过滤时应用它。
当您有多个条件时,使用过滤器的最佳方法是什么?
使用了applicative函数 liftA2 而不是 liftM2,因为我出于某种原因不明白 liftM2 在纯代码中是如何工作的。
【问题讨论】:
函数的过滤器类接受一个条件(a -> Bool)并在过滤时应用它。
当您有多个条件时,使用过滤器的最佳方法是什么?
使用了applicative函数 liftA2 而不是 liftM2,因为我出于某种原因不明白 liftM2 在纯代码中是如何工作的。
【问题讨论】:
嗯,你可以在 Haskell 中组合你想要的函数(只要类型正确),并且使用 lambdas 你甚至不必命名你的谓词函数,即,
filter (\x -> odd x && x > 100) [1..200]
【讨论】:
liftM2 组合器可以在 Reader monad 中使用,以“更实用”的方式执行此操作:
import Control.Monad
import Control.Monad.Reader
-- ....
filter (liftM2 (&&) odd (> 100)) [1..200]
请注意,导入很重要; Control.Monad.Reader 提供了使这一切正常工作的 Monad (e ->) 实例。
之所以可行,是因为对于某些环境 e,reader monad 只是 (e ->)。因此,布尔谓词是在与其参数相对应的环境中返回 bool 的 0 元一元函数。然后我们可以使用 liftM2 将环境分布到两个这样的谓词上。
或者,更简单地说,当类型成功时,liftM2 会表现得有点像这样:
liftM2 f g h a = f (g a) (h a)
如果您希望能够轻松地将它们链接起来,并且/或者不想与 liftM2 混淆,您也可以定义一个新的组合器:
(.&&.) :: (a -> Bool) -> (a -> Bool) -> (a -> Bool)
(.&&.) f g a = (f a) && (g a)
-- or, in points-free style:
(.&&.) = liftM2 (&&)
filter (odd .&&. (> 5) .&&. (< 20)) [1..100]
【讨论】:
Control.Monad.Reader,这两个示例都适用于 GHC 7.6.3。
liftM2(现在)可以这样替换:filter ((&&) <$> odd <*> (>100)) [1.200]。这是相同的,但更漂亮。 :) 它也只需要Control.Applicative,并且不需要完整的单子。 …虽然我仍然想知道什么运算符允许对两个以上的布尔函数进行与运算…
假设您的条件存储在名为conditions 的列表中。此列表的类型为 [a -> Bool]。
要将所有条件应用于值x,您可以使用map:
map ($ x) conditions
这会将每个条件应用于x 并返回一个 Bool 列表。要将此列表简化为单个布尔值,如果所有元素都为 True,则为 True,否则为 False,您可以使用 and 函数:
and $ map ($ x) conditions
现在你有了一个结合所有条件的函数。给它起个名字吧:
combined_condition x = and $ map ($ x) conditions
这个函数的类型是a -> Bool,所以我们可以在调用filter时使用它:
filter combined_condition [1..10]
【讨论】:
all函数:filter (all conditions) [1..10]
all ($ x) conditions,而不是 and $ map ($ x) conditions
如果你有一个a -> Bool 类型的过滤函数列表,并且想将它们组合成一个相同类型的简洁过滤函数,我们可以编写函数来做。您使用以下两个函数中的哪一个将取决于您需要的过滤器行为。
anyfilt :: [(a -> Bool)] -> (a -> Bool)
anyfilt fns = \el -> any (\fn -> fn el) fns
allfilt :: [(a -> Bool)] -> (a -> Bool)
allfilt fns = \el -> all (\fn -> fn el) fns
anyfilt 将在任何过滤器函数返回 true 时返回 true,如果所有过滤器函数返回 false,则返回 false。
allfilt 将在所有过滤器函数都返回 true 时返回 true,而 如果任何过滤器函数返回 false 则返回 false。
请注意,您不能对任一函数进行 η-reduce,因为 RHS 上对 fns 的引用位于匿名函数中。
像这样使用它:
filterLines :: [String] -> [String]
filterLines = let
isComment = isPrefixOf "# "
isBlank = (==) ""
badLine = anyfilt([isComment, isBlank])
in filter (not . badLine)
main = mapM_ putStrLn $ filterLines ["# comment", "", "true line"]
--> "true line"
【讨论】: