Je viens d'écrire les deux fonctions suivantes:
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)
Ils peuvent être utilisés pour combiner les valeurs de deux fonctions booléennes telles que:
import Text.ParserCombinators.Parsec
import Data.Char
nameChar = satisfy (isLetter `f_or` isDigit)
Après avoir examiné ces deux fonctions, je me suis rendu compte qu'elles sont très utiles. à tel point que je soupçonne maintenant qu'ils sont soit inclus dans la bibliothèque standard, soit plus probablement qu'il existe un moyen propre de le faire en utilisant les fonctions existantes.
Quelle était la "bonne" façon de procéder?
Une simplification,
f_and = liftM2 (&&)
f_or = liftM2 (||)
ou
= liftA2 (&&)
= liftA2 (||)
dans le ((->) r)
foncteur applicatif.
version applicable
Pourquoi? Nous avons:
instance Applicative ((->) a) where
(<*>) f g x = f x (g x)
liftA2 f a b = f <$> a <*> b
(<$>) = fmap
instance Functor ((->) r) where
fmap = (.)
Donc:
\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 (&&)
version Monade
Ou pour liftM2
, on a:
instance Monad ((->) r) where
return = const
f >>= k = \ r -> k (f r) r
donc:
\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 (&&)
Totalement arrachant TomMD, j'ai vu le and . map
et or . map
et je n'ai pas pu m'empêcher de le modifier:
fAnd fs x = all ($x) fs
fOr fs x = any ($x) fs
Ceux-ci se lisent bien, je pense. fAnd
: toutes les fonctions sont-elles dans la liste True
lorsque x
leur est appliquée? fOr
: y a-t-il des fonctions dans la liste True
lorsque x
leur est appliquée?
ghci> fAnd [even, odd] 3
False
ghci> fOr [even, odd] 3
True
cependant, c'est un choix de nom étrange. Certainement un bon moyen de jeter ces programmeurs impératifs pour une boucle. =)
C'est plus laid si vous voulez toujours deux fonctions, mais je pense que je le généraliserais:
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
En plus de ce que Don a dit, les versions de liftA2/liftM2
Ne sont peut-être pas assez paresseuses:
> let a .&&. b = liftA2 (&&) a b in pure False .&&. undefined
*** Exception: Prelude.undefined
Woops!
Au lieu de cela, vous voudrez peut-être une fonction légèrement différente. Notez que cette nouvelle fonction nécessite une contrainte Monad
- Applicative
est insuffisante.
> let a *&&* b = a >>= \a' -> if a' then b else return a' in pure False *&&* undefined
False
C'est mieux.
Quant à la réponse qui suggère la fonction on
, c'est pour quand les fonctions sont les mêmes mais les arguments sont différents. Dans votre cas donné, les fonctions sont différentes mais les arguments sont les mêmes. Voici votre exemple modifié afin que on
soit une réponse appropriée:
(f x) && (f y)
qui peut s'écrire:
on (&&) f x y
PS: les parenthèses ne sont pas nécessaires.
Cela peut également être fait en utilisant Flèches :
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
&&&
(sans rapport avec &&
) divise l'entrée; >>>
est la composition de la flèche/catégorie.
Voici un exemple:
> map letter_or_digit "aQ_%8"
[True,True,False,False,True]
Cela fonctionne parce que les fonctions - ->
- sont des instances de Catégorie et Flèche. Comparaison des signatures de type à celles de Don liftA2
et liftM2
des exemples montrent les similitudes:
> :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
Outre le curry, notez que vous pouvez presque convertir le premier type en second en substituant cat a ---> f
et Arrow ---> Applicative
(l'autre différence est que split_combine
ne se limite pas à prendre des fonctions pures dans son 1er argument; probablement pas important cependant).
Cela a en quelque sorte été mentionné, mais d'une manière plus complexe. Vous pouvez utiliser des trucs applicatifs.
Pour les fonctions, il passe essentiellement le même argument à un certain nombre de fonctions que vous pouvez combiner à la fin.
Vous pouvez donc l'implémenter comme ceci:
(&&) <$> aCheckOnA <*> anotherCheckOnA $ a
Pour chaque <*>
dans la chaîne, vous obtenez une autre fonction qui s'applique à a, puis vous fusionnez toutes les sorties en utilisant fmap
alternativement écrit comme <$>
. La raison pour laquelle cela fonctionne avec &&
est parce qu'il faut deux arguments et que nous avons 2 fonctions mises en vedette ensemble. S'il y avait une étoile supplémentaire et un autre chèque, vous devriez écrire quelque chose comme:
(\a b c -> a && b && c) <$> aCheckOnA <*> anotherCheckOnA <*> ohNoNotAnotherCheckOnA $ a
vérifiez ceci pour plus d'exemples