我刚写了以下两个函数:
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)
看了这两个函数后,我意识到它们非常有用。以至于我现在怀疑它们是否包含在标准库中,或者更有可能是使用现有函数有一种干净的方法。
这样做的“正确”方法是什么?
答案 0 :(得分:46)
一种简化,
f_and = liftM2 (&&)
f_or = liftM2 (||)
或
= liftA2 (&&)
= liftA2 (||)
在((->) r)
applicative functor中。
适用版
为什么呢?我们有:
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 (&&)
Monad版
或者对于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 (&&)
答案 1 :(得分: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
答案 2 :(得分:7)
完全扯掉了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
时,列表{{1}}中是否有任何功能?
{{1}}但是,这是一个奇怪的名字选择。当然,为一个循环抛出那些命令式程序员是个好人。 =)
答案 3 :(得分:2)
除了Don所说的,liftA2/liftM2
版本可能不够懒惰:
&GT; let a .&&. b = liftA2 (&&) a b in pure False .&&. undefined
*** Exception: Prelude.undefined
Woops!
所以你可能想要一个稍微不同的功能。请注意,此新功能需要Monad
约束 - Applicative
不足。
&GT; 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:括号是不必要的。
答案 4 :(得分: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的实例。将类型签名与Don的liftA2
和liftM2
示例进行比较可以看出相似之处:
> :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
除了currying之外,请注意您可以通过替换cat a ---> f
和Arrow ---> Applicative
将第一种类型转换为第二种类型(另一个区别是split_combine
不限于在第一个参数中使用纯函数;但可能并不重要。)
答案 5 :(得分:1)
已经提到了一些但是以更复杂的方式。你可以使用适用的东西。
对于函数,基本上它所做的是将相同的参数传递给你可以在最后组合的许多函数。
所以你可以像这样实现它:
(&&) <$> aCheckOnA <*> anotherCheckOnA $ a
对于链中的每个<*>
,您将获得另一个适用于a的函数,然后使用fmap
交替编写为<$>
将所有输出组合在一起。这与&&
一起使用的原因是因为它需要两个参数,我们有两个函数加在一起。如果那里有一个额外的明星和另一个检查,你必须写下这样的东西:
(\a b c -> a && b && c) <$> aCheckOnA <*> anotherCheckOnA <*> ohNoNotAnotherCheckOnA $ a
check this out了解更多示例
答案 6 :(得分:-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)
(来自Hoogle)