在 Haskell 中对布尔函数执行“and”和“or”

发布于 2024-11-02 01:16:21 字数 505 浏览 6 评论 0原文

我刚刚编写了以下两个函数:

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)

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

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

I just wrote the following two functions:

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)

They might be used to combined the values of two boolean functions such as:

import Text.ParserCombinators.Parsec
import Data.Char

nameChar = satisfy (isLetter `f_or` isDigit)

After looking at these two functions, I came to the realization that they are very useful. so much so that I now suspect that they are either included in the standard library, or more likely that there is a clean way to do this using existing functions.

What was the "right" way to do this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

我不会写诗 2024-11-09 01:16:22

这一点已经被提到过,但是是以一种更复杂的方式。你可以使用应用性的东西。

对于函数来说,它的作用基本上是将相同的参数传递给多个可以在最后组合的函数。

因此,您可以这样实现它:

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

对于链中的每个 <*> ,您将获得另一个适用于 a 的函数,然后使用 fmap 也可以写为 <$>。它与 && 一起使用的原因是因为它需要两个参数,并且我们有 2 个函数一起加星标。如果那里有一个额外的星星和另一个检查,你必须写一些类似的东西:

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

查看此以获取更多示例

This has sortof been mentioned but in a more complex way. You could use applicative stuff.

For functions basically what it does is pass the same argument to a number of functions that you can combine at the end.

So you could implement it like this:

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

For each <*> in the chain then you get another function that applies to a and then you munge all the output together using fmap alternately written as <$>. The reason this works with && is because it takes two arguments and we have 2 functions starred together. If there was an extra star in there and another check you'd have to write something like:

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

check this out for more examples

霞映澄塘 2024-11-09 01:16:22

除了 Don 所说的之外,liftA2/liftM2 版本可能还不够懒:

> 让 .&&. b = liftA2 (&&) ab 为纯 False .&&。未定义

*** 异常:Prelude.undefined

糟糕!

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

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

False

这样更好。

至于建议使用 on 函数的答案,这是针对函数相同但参数不同的情况。在您给定的情况下,功能不同,但参数相同。这是您的示例,经过修改,on 是一个合适的答案:

(fx) && (fy)

可以写成:

on (&&) fx y

PS:括号是不必要的。

On top of what Don said, the liftA2/liftM2 versions may not be lazy enough:

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

*** Exception: Prelude.undefined

Woops!

So instead you might want a slightly different function. Note that this new function requires a Monad constraint -- Applicative is insufficient.

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

False

That's better.

As for the answer that suggests the on function, this is for when the functions are the same but the arguments are different. In your given case, the functions are different but the arguments are the same. Here is your example altered so that on is an appropriate answer:

(f x) && (f y)

which can be written:

on (&&) f x y

PS: the parentheses are unnecessary.

女皇必胜 2024-11-09 01:16:22

这也可以使用 箭头 来完成:

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 的 liftA2liftM2 示例进行比较显示出相似之处:

> :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 将第一种类型转换为第二种类型一个---> f箭头 --->应用性(另一个区别是 split_combine 不限于在第一个参数中采用纯函数;但可能并不重要)。

This can also be done using 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

&&& (not related to &&) splits the input; >>> is arrow/category composition.

Here's an example:

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

This works because functions -- -> -- are instances of Category and Arrow. Comparing the type signatures to Don's liftA2 and liftM2 examples shows the similarities:

> :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

Besides the currying, note that you can almost convert the first type into the second by substituting cat a ---> f and Arrow ---> Applicative (the other difference is that split_combine isn't limited to taking pure functions in its 1st argument; probably not important though).

自我难过 2024-11-09 01:16:22

如果 f1 和 f2 相同,则可以

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

在基础 Data.Function 中

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

使用 'on':典型用法:(

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

来自

If f1 and f2 are the same, then you can use 'on':

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

in base Data.Function

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

Typical usage:

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

(from Hoogle)

岁吢 2024-11-09 01:16:21

一种简化,

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 <
gt; a <*> b

(<
gt;) = fmap

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

所以:

  \f g -> liftA2 (&&) f g
= \f g -> (&&) <
gt; f <*> g          -- defn of liftA2
= \f g -> ((&&) . f) <*> g          -- defn of <
gt;
= \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 (&&)

One simplification,

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

or

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

in the ((->) r) applicative functor.


Applicative version

Why? We have:

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

liftA2 f a b = f <
gt; a <*> b

(<
gt;) = fmap

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

So:

  \f g -> liftA2 (&&) f g
= \f g -> (&&) <
gt; f <*> g          -- defn of liftA2
= \f g -> ((&&) . f) <*> g          -- defn of <
gt;
= \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 version

Or for liftM2, we have:

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

so:

  \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 (&&)
云淡月浅 2024-11-09 01:16:21

完全抄袭 TomMD,我看到了 和 .地图或 .地图并且忍不住想要调整它:

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

我认为这些读起来很好。 fAnd:当x应用于列表中的所有函数时,它们是否都TruefOr:当 x 应用于列表中时,列表中的任何函数是否为 True

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

不过,fOr 是一个奇怪的名字选择。这当然是让那些命令式程序员陷入困境的好方法。 =)

Totally ripping off of TomMD, I saw the and . map and or . map and couldn't help but want to tweak it:

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

These read nicely I think. fAnd: are all functions in the list True when x is applied to them? fOr: are any functions in the list True when x is applied to them?

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

fOr is an odd name choice, though. Certainly a good one to throw those imperative programmers for a loop. =)

紫﹏色ふ单纯 2024-11-09 01:16:21

如果你总是想要两个函数,那就更丑了,但我想我会概括它:

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

It's uglier if you always want two functions, but I think I'd generalize it:

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文