web-dev-qa-db-ja.com

Haskellでは、ブール関数に対して「and」と「or」を実行します

次の2つの関数を作成しました。

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)

これらは、次のような2つのブール関数の値を結合するために使用される場合があります。

import Text.ParserCombinators.Parsec
import Data.Char

nameChar = satisfy (isLetter `f_or` isDigit)

これらの2つの関数を見て、それらが非常に有用であることに気付きました。そのため、標準ライブラリに含まれているか、または既存の関数を使用してこれを行うクリーンな方法がある可能性が高いと思われます。

これを行う「正しい」方法は何ですか?

40
John F. Miller

1つの単純化、

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 (&&)
46
Don Stewart

完全に TomMDをリッピングすると、and . mapおよびor . mapそして仕方がなかったが、それを微調整したい:

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

これらはうまく読みます。 fAndTrueが適用されている場合、リスト内のすべての関数はxですか? fOrTrueが適用されている場合、リスト内の関数はxですか?

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

ただし、fOrは奇妙な名前の選択です。 loopのためにこれらの命令型プログラマーを投げるのは確かに良いものです。 =)

8
Dan Burton

常に2つの関数が必要な場合はいですが、一般化すると思います。

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
7

ドンが言ったことに加えて、_liftA2/liftM2_バージョンは十分に怠enoughではないかもしれません:

> 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:括弧は不要です。

2
Tony Morris

これは 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

カリー化に加えて、cat a ---> fArrow ---> Applicativeを置き換えることで、最初の型をほぼ2番目の型に変換できることに注意してください(他の違いは、split_combineは、最初の引数;ただし、おそらく重要ではありません)。

1
Matt Fenwick

これについては言及されていますが、より複雑な方法です。適用可能なものを使用できます。

関数の場合、基本的には、最後に組み合わせることができる多くの関数に同じ引数を渡します。

したがって、次のように実装できます。

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

チェーンの各<*>に対して、aに適用される別の関数を取得し、<$>として交互に記述されたfmapを使用して、すべての出力をまとめます。これが&&で機能する理由は、2つの引数を取り、2つの関数にスターを付けるためです。そこに余分な星があり、別のチェックがあった場合、次のように書く必要があります。

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

チェックアウト 他の例

1
JonnyRaa