在Haskell中为布尔函数执行`and`和`or`
我只写了以下两个函数:
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)
看了这两个函数之后,我意识到它们非常有用。 以至于我现在怀疑它们被包含在标准库中,或者更可能是使用现有函数来执行此操作的干净方式。
什么是“正确”的方式来做到这一点?
一个简化,
f_and = liftM2 (&&) f_or = liftM2 (||)
要么
= liftA2 (&&) = liftA2 (||)
在((->) r)
应用函数中。
适用版本
为什么? 我们有:
instance Applicative ((->) a) where (<*>) fgx = fx (gx) liftA2 fab = f <$> a <*> b (<$>) = fmap instance Functor ((->) r) where fmap = (.)
所以:
\fg -> liftA2 (&&) fg = \fg -> (&&) <$> f <*> g -- defn of liftA2 = \fg -> ((&&) . f) <*> g -- defn of <$> = \fgx -> (((&&) . f) x) (gx) -- defn of <*> - (.) fg = \x -> f (gx) = \fgx -> ((&&) (fx)) (gx) -- defn of (.) = \fgx -> (fx) && (gx) -- infix (&&)
Monad版本
或者对于liftM2
,我们有:
instance Monad ((->) r) where return = const f >>= k = \ r -> k (fr) r
所以:
\fg -> liftM2 (&&) fg = \fg -> do { x1 <- f; x2 <- g; return ((&&) x1 x2) } -- defn of liftM2 = \fg -> f >>= \x1 -> g >>= \x2 -> return ((&&) x1 x2) -- by do notation = \fg -> (\r -> (\x1 -> g >>= \x2 -> return ((&&) x1 x2)) (fr) r) -- defn of (>>=) = \fg -> (\r -> (\x1 -> g >>= \x2 -> const ((&&) x1 x2)) (fr) r) -- defn of return = \fg -> (\r -> (\x1 -> (\r -> (\x2 -> const ((&&) x1 x2)) (gr) r)) (fr) r) -- defn of (>>=) = \fgx -> (\r -> (\x2 -> const ((&&) (fx) x2)) (gr) r) x -- beta reduce = \fgx -> (\x2 -> const ((&&) (fx) x2)) (gx) x -- beta reduce = \fgx -> const ((&&) (fx) (gx)) x -- beta reduce = \fgx -> ((&&) (fx) (gx)) -- defn of const = \fgx -> (fx) && (gx) -- inline (&&)
如果你总是需要两个函数,那就更丑陋了,但我想我会推广它:
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
完全撕下了TomMD,我看到了and . map
and . map
和or . map
or . map
,忍不住想调整一下:
fAnd fs x = all ($x) fs fOr fs x = any ($x) fs
我认为这些阅读很好。 fAnd
:列表中的所有函数是否将x
应用于它们时为True
? fOr
:列表中的任何函数是否应用x
为True
?
ghci> fAnd [even, odd] 3 False ghci> fOr [even, odd] 3 True
但是,这是一个奇怪的名字select。 肯定是一个很好的扔这些命令程序员的循环 。 =)
在Don的说法之上, liftA2/liftM2
版本可能不够懒惰:
> let a .&&. b = liftA2 (&&) ab in pure False .&&. undefined
let a .&&. b = liftA2 (&&) ab in pure False .&&. undefined
*** Exception: Prelude.undefined
Woops!
所以,你可能需要一个稍微不同的function。 请注意,这个新function需要Monad
约束 – Applicative
是不够的。
> let a *&&* b = a >>= \a' -> if a' then b else return a' in pure False *&&* undefined
False
好多了
至于表示on
函数的答案,这是针对函数相同但参数不同的答案。 在你的情况下,function是不同的,但论据是相同的。 这是你的例子改变,以便on
一个合适的答案:
(fx) && (fy)
可以这样写:
on (&&) fxy
PS:括号是不必要的。
这也可以使用箭头来完成:
import Control.Arrow ((&&&), (>>>), Arrow(..)) split_combine :: Arrow cat => cat (b, c) d -> cat ab -> cat ac -> cat ad split_combine hfg = (f &&& g) >>> h letter_or_digit = split_combine (uncurry (||)) isLetter isDigit
&&&
(与&&
无关)拆分input; >>>
是箭头/类别组合。
这是一个例子:
> map letter_or_digit "aQ_%8" [True,True,False,False,True]
这是有效的,因为函数 – ->
– 是类别和箭头的实例。 比较types签名与唐的liftA2
和liftM2
例子显示了相似之处:
> :t split_combine split_combine :: Arrow cat => cat (b, c) d -> cat ab -> cat ac -> cat ad > :t liftA2 liftA2 :: Applicative f => (b -> c -> d) -> fb -> fc -> fd
除了currying,注意你几乎可以把第一个types转换成第二个types,用cat a ---> f
和Arrow ---> Applicative
来代替(另一个区别是split_combine
并不局限于在第一个函数中使用纯函数参数;虽然可能不重要)。
这已经被提及,但是以更复杂的方式提到。 你可以使用应用的东西。
对于函数来说,它所做的基本function是将相同的parameter passing给一些可以在最后结合使用的函数。
所以你可以像这样实现它:
(&&) <$> aCheckOnA <*> anotherCheckOnA $ a
对于链中的每个<*>
,您将得到另一个适用于a的函数,然后使用交替写入为<$>
fmap
所有输出混合在一起。 与&&
工作的原因是因为它需要两个参数,而且我们有两个函数一起出演。 如果在那里有一个额外的星星和另一个支票,你必须写下类似的东西:
(\abc -> a && b && c) <$> aCheckOnA <*> anotherCheckOnA <*> ohNoNotAnotherCheckOnA $ a
检查这个更多的例子
如果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 )