44

我只是写了以下两个函数:

fand :: (a -> Bool) -> (a -> Bool) -> a -> Bool
fand f1 f2 x = (f1 x) && (f2 x)

f_or :: (a -> Bool) -> (a -> Bool) -> a -> Bool
f_or f1 f2 x = (f1 x) || (f2 x)

它们可用于组合两个布尔函数的值,例如:

import Text.ParserCombinators.Parsec
import Data.Char

nameChar = satisfy (isLetter `f_or` isDigit)

在看了这两个函数之后,我意识到它们非常有用。如此之多,以至于我现在怀疑它们要么包含在标准库中,要么更有可能是使用现有函数进行此操作的干净方法。

这样做的“正确”方法是什么?

4

7 回答 7

49

一种简化,

f_and = liftM2 (&&)
f_or  = liftM2 (||)

或者

      = liftA2 (&&)         
      = liftA2 (||)

((->) r)应用函子中。


适用版本

为什么?我们有:

instance Applicative ((->) a) where
    (<*>) f g x = f x (g x)

liftA2 f a b = f <$> a <*> b

(<$>) = fmap

instance Functor ((->) r) where
    fmap = (.)

所以:

  \f g -> liftA2 (&&) f g
= \f g -> (&&) <$> f <*> g          -- defn of liftA2
= \f g -> ((&&) . f) <*> g          -- defn of <$>
= \f g x -> (((&&) . f) x) (g x)    -- defn of <*> - (.) f g = \x -> f (g x)
= \f g x -> ((&&) (f x)) (g x)      -- defn of (.)
= \f g x -> (f x) && (g x)          -- infix (&&)

单子版本

或者对于liftM2,我们有:

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

所以:

  \f g -> liftM2 (&&) f g
= \f g -> do { x1 <- f; x2 <- g; return ((&&) x1 x2) }               -- defn of liftM2
= \f g -> f >>= \x1 -> g >>= \x2 -> return ((&&) x1 x2)              -- by do notation
= \f g -> (\r -> (\x1 -> g >>= \x2 -> return ((&&) x1 x2)) (f r) r)  -- defn of (>>=)
= \f g -> (\r -> (\x1 -> g >>= \x2 -> const ((&&) x1 x2)) (f r) r)   -- defn of return
= \f g -> (\r -> (\x1 ->
               (\r -> (\x2 -> const ((&&) x1 x2)) (g r) r)) (f r) r) -- defn of (>>=)
= \f g x -> (\r -> (\x2 -> const ((&&) (f x) x2)) (g r) r) x         -- beta reduce
= \f g x -> (\x2 -> const ((&&) (f x) x2)) (g x) x                   -- beta reduce
= \f g x -> const ((&&) (f x) (g x)) x                               -- beta reduce
= \f g x -> ((&&) (f x) (g x))                                       -- defn of const
= \f g x -> (f x) && (g x)                                           -- inline (&&)
于 2011-04-18T23:53:48.957 回答
9

完全扯掉了 TomMD,我看到了and . map并且or . map忍不住想要调整它:

fAnd fs x = all ($x) fs
fOr fs x = any ($x) fs

我认为这些读起来很好。fAnd: 列表中的所有函数True何时x应用于它们?fOr: 列表中的任何函数True何时x应用于它们?

ghci> fAnd [even, odd] 3
False
ghci> fOr [even, odd] 3
True

不过,fOr 是一个奇怪的名称选择。让那些命令式程序员陷入循环当然是一个不错的选择。=)

于 2011-04-19T01:33:56.430 回答
7

如果您总是想要两个功能,那就更难看了,但我想我会概括它:

mapAp fs x = map ($x) fs

fAnd fs = and . mapAp fs
fOr fs = or . mapAp fs

> fOr [(>2), (<0), (== 1.1)] 1.1
True
> fOr [(>2), (<0), (== 1.1)] 1.2
False
> fOr [(>2), (<0), (== 1.1)] 4
True
于 2011-04-18T23:59:43.903 回答
3

除了 Don 所说的,这些liftA2/liftM2版本可能还不够懒惰:

>let a .&&. b = liftA2 (&&) a b in pure False .&&. undefined

*** Exception: Prelude.undefined

哎呀!

因此,您可能想要一个稍微不同的功能。请注意,这个新功能需要一个Monad约束——Applicative是不够的。

>let a *&&* b = a >>= \a' -> if a' then b else return a' in pure False *&&* undefined

False

这样更好。

至于建议on功能的答案,这是针对功能相同但参数不同的情况。在您给定的情况下,功能不同但参数相同。这是您的示例已更改,因此这on是一个适当的答案:

(f x) && (f y)

可以写成:

on (&&) f x y

PS:括号是不必要的。

于 2011-04-19T10:23:30.777 回答
3

这有点被提及,但方式更复杂。你可以使用应用程序的东西。

对于函数,基本上它所做的是将相同的参数传递给最后可以组合的多个函数。

所以你可以像这样实现它:

(&&) <$> aCheckOnA <*> anotherCheckOnA $ a

对于<*>链中的每个,您将获得另一个适用于 a 的函数,然后您将所有输出组合在一起使用fmap交替书写的 as <$>。之所以这样,&&是因为它需要两个参数,并且我们有两个函数一起加星标。如果那里有一颗额外的星星和另一张支票,你必须写下类似的东西:

(\a b c -> a && b && c) <$> aCheckOnA <*> anotherCheckOnA <*> ohNoNotAnotherCheckOnA $ a

查看更多示例

于 2015-02-25T15:35:31.547 回答
1

这也可以使用Arrows来完成:

import Control.Arrow ((&&&), (>>>), Arrow(..))

split_combine :: Arrow cat => cat (b, c) d -> cat a b -> cat a c -> cat a d
split_combine h f g = (f &&& g) >>> h

letter_or_digit = split_combine (uncurry (||)) isLetter isDigit

&&&(不相关&&)分割输入;>>>是箭头/类别组成。

这是一个例子:

> map letter_or_digit "aQ_%8"
[True,True,False,False,True]

这是因为函数 -- ->-- 是 Category 和 Arrow 的实例。将类型签名与 DonliftA2liftM2示例进行比较显示了相似之处:

> :t split_combine 
split_combine :: Arrow cat => cat (b, c) d  -> cat a b -> cat a c -> cat a d

> :t liftA2
liftA2    :: Applicative f => (b -> c -> d) ->     f b ->     f c ->     f d

除了柯里化之外,请注意,您几乎可以通过替换cat a ---> fand将第一种类型转换为第二种类型Arrow ---> Applicative(另一个区别split_combine不限于在其第一个参数中采用纯函数;尽管可能并不重要)。

于 2012-11-28T15:38:39.440 回答
-1

如果 f1 和 f2 相同,则可以使用 'on':

on :: (b -> b -> c) -> (a -> b) -> a -> a -> c

在基础 Data.Function 中

fand1 f = (&&) `on` f
for1 f = (||) `on` f

典型用法:

Data.List.sortBy (compare `on` fst)

(来自胡格尔

于 2011-04-19T00:00:27.967 回答