次の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つの関数を見て、それらが非常に有用であることに気付きました。そのため、標準ライブラリに含まれているか、または既存の関数を使用してこれを行うクリーンな方法がある可能性が高いと思われます。
これを行う「正しい」方法は何ですか?
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 (&&)
完全に 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は奇妙な名前の選択です。 loopのためにこれらの命令型プログラマーを投げるのは確かに良いものです。 =)
常に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
ドンが言ったことに加えて、_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:括弧は不要です。
これは 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 ---> f
とArrow ---> Applicative
を置き換えることで、最初の型をほぼ2番目の型に変換できることに注意してください(他の違いは、split_combine
は、最初の引数;ただし、おそらく重要ではありません)。
これについては言及されていますが、より複雑な方法です。適用可能なものを使用できます。
関数の場合、基本的には、最後に組み合わせることができる多くの関数に同じ引数を渡します。
したがって、次のように実装できます。
(&&) <$> aCheckOnA <*> anotherCheckOnA $ a
チェーンの各<*>
に対して、aに適用される別の関数を取得し、<$>
として交互に記述されたfmap
を使用して、すべての出力をまとめます。これが&&
で機能する理由は、2つの引数を取り、2つの関数にスターを付けるためです。そこに余分な星があり、別のチェックがあった場合、次のように書く必要があります。
(\a b c -> a && b && c) <$> aCheckOnA <*> anotherCheckOnA <*> ohNoNotAnotherCheckOnA $ a
チェックアウト 他の例