web-dev-qa-db-fra.com

Dans Haskell, effectuer `et` et` ou `pour les fonctions booléennes

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?

40
John F. Miller

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

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. =)

8
Dan Burton

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
7

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.

2
Tony Morris

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).

1
Matt Fenwick

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

1
JonnyRaa