Résumé
Nous avons besoin de définir (instances qui fournissent les mêmes opérations que) Monoïde cas pour certains foncteurs applicatifs, sincèrement, combiner le foncteur applicatif de niveau, et pas seulement de levage niveau inférieur monoids. L'exemple d'erreur ci-dessous à partir de litvar = liftA2 mappend literal variable
montre que <|>
ne peut en général être défini comme l' liftA2 mappend
; <|>
fonctionne dans ce cas, par la combinaison d'analyseurs, pas leurs données.
Si l'on utilise le Monoïde directement, nous aurions besoin d'extensions de langage pour définir les instances. Alternative
est supérieur kinded de sorte que vous pouvez faire ces cas, sans exiger des extensions de langage.
Exemple: Analyseurs
Imaginons que nous faisons l'analyse des déclarations, de sorte que nous importons tout ce que nous allons avoir besoin de
import Text.Parsec
import Text.Parsec.String
import Control.Applicative ((<$>),(<*>),liftA2,empty)
import Data.Monoid
import Data.Char
et de réfléchir à la façon dont nous allons analyser un type. Nous choisissons simpliste:
data Type = Literal String | Variable String deriving Show
examples = [Literal "Int",Variable "a"]
Maintenant, nous allons écrire un analyseur de deux types:
literal :: Parser Type
literal = fmap Literal $ (:) <$> upper <*> many alphaNum
Sens: analyser un upper
de cas de caractères, alors many alphaNum
eric personnages, combiner les résultats dans une seule Chaîne avec la fonction pure, (:)
. Ensuite, appliquer la fonction pure, Literal
les String
s dans Type
s. Nous allons analyser les types de variables exactement de la même façon, sauf pour le démarrage avec un lower
de cas lettre:
variable :: Parser Type
variable = fmap Variable $ (:) <$> lower <*> many alphaNum
C'est excellent, et parseTest literal "Bool" == Literal "Bool"
exactement comme nous l'espérions.
La Question 3a: Si c'est de combiner applicative effets avec Monoïde de comportement, pourquoi ne pas simplement liftA2 mappend
Edit:Oups - oublié de l'utiliser réellement <|>
!
Maintenant, nous allons combiner ces deux analyseurs à l'aide d'autres:
types :: Parser Type
types = literal <|> variable
Cela peut analyser n'importe quel Type: parseTest types "Int" == Literal "Bool"
et parseTest types "a" == Variable "a"
.
Cette combine les deux analyseurs, pas les deux valeurs. C'est le sens dans lequel il travaille à l'Applicatif Foncteur niveau, plutôt que le niveau de données.
Cependant, si nous essayons de nous:
litvar = liftA2 mappend literal variable
ce serait de demander au compilateur de combiner les deux valeurs qu'ils génèrent, au niveau des données.
Nous obtenons
No instance for (Monoid Type)
arising from a use of `mappend'
Possible fix: add an instance declaration for (Monoid Type)
In the first argument of `liftA2', namely `mappend'
In the expression: liftA2 mappend literal variable
In an equation for `litvar':
litvar = liftA2 mappend literal variable
Nous avons donc découvert la première chose; l'autre classe n'quelque chose de véritablement différent de liftA2 mappend
,, car il combine des objets à un niveau différent - il combine les analyseurs, pas de l'analyse de données. Si vous aimez à penser à elle de cette façon, c'est une combinaison à la véritable plus-type de niveau, et non pas simplement un ascenseur. Je n'aime pas dire cela de cette façon, parce qu' Parser Type
a *
, mais il est vrai de dire que nous sommes en combinant l' Parser
s, pas le Type
s.
(Même pour les types avec un Monoïde exemple, liftA2 mappend
ne vous donnera pas le même analyseur <|>
. Si vous l'essayez sur Parser String
vous obtiendrez liftA2 mappend
qui analyse l'un après l'autre puis concatène, contre <|>
qui va tenter le premier et l'analyseur de défaut à la seconde si elle a échoué.)
Question 3b: En quoi l'Alternative de l' <|> :: f a -> f a -> f a
diffèrent de Monoïde de l' mappend :: b -> b -> b
?
Tout d'abord, vous avez raison de noter qu'il n'a pas à fournir de nouvelles fonctionnalités sur un Monoïde instance.
Deuxièmement, cependant, il y a un problème avec l'utilisation de Monoïde directement:
Nous allons essayer d'utiliser mappend
sur les analyseurs, en même temps que de montrer que c'est la même structure que l' Alternative
:
instance Monoid (Parser a) where
mempty = empty
mappend = (<|>)
Oups! Nous obtenons
Illegal instance declaration for `Monoid (Parser a)'
(All instance types must be of the form (T t1 ... tn)
where T is not a synonym.
Use -XTypeSynonymInstances if you want to disable this.)
In the instance declaration for `Monoid (Parser a)'
Donc, si vous avez un foncteur applicatif f
, l' Alternative
exemple montre que l' f a
est un monoïde, mais vous ne peut que déclarer que, en tant que Monoid
avec une extension du langage.
Une fois que nous ajoutons {-# LANGUAGE TypeSynonymInstances #-}
dans le haut du fichier, nous allons bien, et peut définir
typeParser = literal `mappend` variable
et pour notre plus grand plaisir, il travaille: parseTest typeParser "Yes" == Literal "Yes"
et parseTest typeParser "a" == Literal "a"
.
Même si vous n'avez pas de synonymes (Parser
et String
sont des synonymes, de sorte qu'ils sont), vous aurez toujours besoin d' {-# LANGUAGE FlexibleInstances #-}
à définir une instance comme celle-ci:
data MyMaybe a = MyJust a | MyNothing deriving Show
instance Monoid (MyMaybe Int) where
mempty = MyNothing
mappend MyNothing x = x
mappend x MyNothing = x
mappend (MyJust a) (MyJust b) = MyJust (a + b)
(Le monoïde exemple, Peut-être est-ce en soulevant le sous-jacent monoïde.)
Faire une bibliothèque standard inutilement dépend des extensions de langage n'est évidemment pas souhaitable.
Donc là vous l'avez. Alternative est juste Monoïde pour les Foncteurs Applicatifs (et ce n'est pas seulement un ascenseur, d'un Monoïde). Il a besoin de la plus-kinded type f a -> f a -> f a
de sorte que vous pouvez définir l'un sans les extensions de langage.
Vos autres Questions, par souci d'exhaustivité:
Pourquoi ne Alternatifs besoin d'une méthode vide/membre?
Parce qu'avoir une identité pour une opération est parfois utile.
Par exemple, vous pouvez définir anyA = foldr (<|>) empty
sans l'utilisation fastidieuse des cas limites.
quel est le point de la MonadPlus type de classe? Je ne peux pas déverrouiller tous les de sa bonté, juste en utilisant quelque chose comme une Monade et Alternative?
Pas de. Je vous renvoie à la question que vous avez lié à:
En outre, même si Applicative a été une super-classe de la Monade, vous feriez liquidation nécessitant la MonadPlus classe de toute façon, parce que l'obéissance empty <*> m = empty
n'est pas strictement suffisant pour prouver qu' empty >>= f = empty
.
....et je suis venu avec un exemple: Peut-être. Je l'explique en détail, avec la preuve dans cette réponse à Antal la question. Pour les fins de cette réponse, il est intéressant de noter que j'ai été en mesure d'utiliser >>= pour faire de la MonadPlus instance, qui a battu l'Alternative des lois.
Structure de monoïde est utile. L'Alternative est la meilleure façon de fournir pour les Foncteurs Applicatifs.