La Applicative
classe de types représente des foncteurs monoïdes laxistes qui préservent la structure monoïde cartésienne sur la catégorie des fonctions typées.
En d'autres termes, étant donné les isomorphismes canoniques témoins qui (,)
forment une structure monoïdale:
-- Implementations left to the motivated reader
assoc_fwd :: ((a, b), c) -> (a, (b, c))
assoc_bwd :: (a, (b, c)) -> ((a, b), c)
lunit_fwd :: ((), a) -> a
lunit_bwd :: a -> ((), a)
runit_fwd :: (a, ()) -> a
runit_bwd :: a -> (a, ())
La classe de types et ses lois peuvent être écrites de manière équivalente comme ceci:
class Functor f => Applicative f
where
zip :: (f a, f b) -> f (a, b)
husk :: () -> f ()
-- Laws:
-- assoc_fwd >>> bimap id zip >>> zip
-- =
-- bimap zip id >>> zip >>> fmap assoc_fwd
-- lunit_fwd
-- =
-- bimap husk id >>> zip >>> fmap lunit_fwd
-- runit_fwd
-- =
-- bimap id husk >>> zip >>> fmap runit_fwd
On peut se demander à quoi pourrait ressembler un fonctor monoïde oplax par rapport à la même structure:
class Functor f => OpApplicative f
where
unzip :: f (a, b) -> (f a, f b)
unhusk :: f () -> ()
-- Laws:
-- assoc_bwd <<< bimap id unzip <<< unzip
-- =
-- bimap unzip id <<< unzip <<< fmap assoc_bwd
-- lunit_bwd
-- =
-- bimap unhusk id <<< unzip <<< fmap lunit_bwd
-- runit_bwd
-- =
-- bimap id unhusk <<< unzip <<< fmap runit_bwd
Si nous pensons aux types impliqués dans les définitions et les lois, la vérité décevante est révélée; OpApplicative
n'est pas une contrainte plus spécifique que Functor
:
instance Functor f => OpApplicative f
where
unzip fab = (fst <$> fab, snd <$> fab)
unhusk = const ()
Cependant, alors que chaque Applicative
foncteur (vraiment, n'importe lequel Functor
) est trivialement OpApplicative
, il n'y a pas nécessairement une bonne relation entre les Applicative
laxités et les OpApplicative
oplaxités. Nous pouvons donc rechercher de solides foncteurs monoïdaux par rapport à la structure monoïde cartésienne:
class (Applicative f, OpApplicative f) => StrongApplicative f
-- Laws:
-- unhusk . husk = id
-- husk . unhusk = id
-- zip . unzip = id
-- unzip . zip = id
La première loi ci-dessus est triviale, car le seul habitant du type () -> ()
est la fonction d'identité ()
.
Cependant, les trois lois restantes, et donc la sous-classe elle-même, ne sont pas triviales. Plus précisément, tous ne sont pas Applicative
une instance légale de cette classe.
Voici quelques Applicative
foncteurs pour lesquels nous pouvons déclarer des instances licites de StrongApplicative
:
Identity
VoidF
(->) r
(voir les réponses)Monoid m => (,) m
Vec (n :: Nat)
Stream
(infini)
Et voici quelques Applicative
s pour lesquels nous ne pouvons pas:
[]
Either e
Maybe
NonEmptyList
Le schéma suggère ici que la StrongApplicative
classe est en quelque sorte la FixedSize
classe, où "taille fixe" * signifie que la multiplicité ** d'habitants d' a
un habitant de f a
est fixe.
Cela peut être défini comme deux conjectures:
- Chaque
Applicative
représentant un conteneur "de taille fixe" d'éléments de son argument type est une instance deStrongApplicative
- Aucun cas de
StrongApplicative
existe dans lequel le nombre d'occurrences dea
peut varier
Quelqu'un peut-il penser à des contre-exemples qui réfutent ces conjectures, ou à un raisonnement convaincant qui démontre pourquoi elles sont vraies ou fausses?
* Je me rends compte que je n'ai pas correctement défini l'adjectif "taille fixe". Malheureusement, la tâche est un peu circulaire. Je ne connais aucune description formelle d'un conteneur de "taille fixe" et j'essaie d'en trouver un. StrongApplicative
est ma meilleure tentative jusqu'à présent.
Cependant, afin d'évaluer si c'est une bonne définition, j'ai besoin de quelque chose à comparer. Étant donné une définition formelle / informelle de ce que signifie pour un foncteur d'avoir une taille ou une multiplicité donnée par rapport aux habitants de son argument de type, la question est de savoir si l'existence d'une StrongApplicative
instance distingue précisément les foncteurs de taille fixe et variable.
Ne connaissant pas une définition formelle existante, je fais appel à l'intuition dans mon utilisation du terme «taille fixe». Cependant, si quelqu'un connaît déjà un formalisme existant pour la taille d'un foncteur et peut le comparer StrongApplicative
, tant mieux.
** Par "multiplicité", je me réfère au sens large à "combien" d'éléments arbitraires du type de paramètre du foncteur se produisent chez un habitant du type codomaine du foncteur. Ceci est sans égard au type spécifique auquel le foncteur est appliqué, et donc sans égard aux habitants spécifiques du type de paramètre.
Ne pas être précis à ce sujet a provoqué une certaine confusion dans les commentaires, voici donc quelques exemples de ce que je considérerais comme la taille / multiplicité de divers foncteurs:
VoidF
: fixe, 0Identity
: fixe, 1Maybe
: variable, minimum 0, maximum 1[]
: variable, minimum 0, maximum infiniNonEmptyList
: variable, minimum 1, maximum infiniStream
: fixe, infiniMonoid m => (,) m
: fixe, 1data Pair a = Pair a a
: fixe, 2Either x
: variable, minimum 0, maximum 1data Strange a = L a | R a
: fixe, 1
la source
(->) r
ils sont isomorphes dans le bon sens.(->) r
; vous avez besoin des composants de l'isomorphisme pour préserver la structure applicative solide. Pour une raison quelconque, laRepresentable
classe de caractères de Haskell a unetabulate . return = return
loi mystérieuse (qui n'a même pas vraiment de sens pour les foncteurs non monadiques), mais elle nous donne 1/4 des conditions que nous devons diretabulate
etzip
sont des morphismes d'une catégorie appropriée de monoïdes . Les 3 autres sont des lois supplémentaires que vous devez exiger.tabulate
etindex
sont des morphismes d'une catégorie appropriée ..."return
n'est pas un problème grave.cotraverse getConst . Const
est une implémentation par défaut pourreturn
/pure
en termes deDistributive
, et, comme les distributifs / représentables ont une forme fixe, cette implémentation est unique.Réponses:
Je ne suis pas sûr de cette première conjecture, et d'après les discussions avec @AsadSaeeduddin, il sera probablement difficile à prouver, mais la deuxième conjecture est vraie. Pour voir pourquoi, considérez la
StrongApplicative
loihusk . unhusk == id
; qui est, pour tousx :: f ()
,husk (unhusk x) == x
. Mais dans Haskell,unhusk == const ()
, de sorte que la loi équivaut à dire pour tousx :: f ()
,husk () == x
. Mais cela implique à son tour qu'il ne peut exister qu'une seule valeur distincte de typef ()
: s'il y avait deux valeursx, y :: f ()
, alorsx == husk ()
ethusk () == y
, alorsx == y
. Mais s'il n'y a qu'une seulef ()
valeur possible , alors ellef
doit être de forme fixe. (par exemple pourdata Pair a = Pair a a
, il n'y a qu'une seule valeur de typePair ()
, ceci étantPair () ()
, mais il y a plusieurs valeurs de typeMaybe ()
ou[()]
.) Ainsihusk . unhusk == id
implique qu'ilf
doit être de forme fixe.la source
f ()
" implique "le nombre d'occurrences dea
ne peut pas varier" en présence de GADT fantaisistes et d'autres choses?a
ne peut pas varier" n'est pas une condition suffisante pour uneStrongApplicative
instance; par exemple,data Writer w a = Writer (w,a)
a une multiplicité non variable dea
, mais n'est pas aStrongApplicative
. En fait, vous avez besoin que la forme du foncteur soit invariante, ce qui, à mon avis, est la conséquence d'f ()
être un singleton.f ()
" implique "le nombre d'occurrences dea
ne peut pas varier". J'objecte que la dernière étape de cet argument n'est pas clairement vraie; par exemple considérerdata Weird a where One :: a -> Weird a; None :: Weird Bool
. Il y a une valeur distincte de typeWeird ()
, mais différents constructeurs ont un nombre variable dea
s à l'intérieur. (Ce n'est pas un contre-exemple complet ici parce queFunctor
c'est difficile, mais comment savons-nous que cela ne peut pas être corrigé?)Weird ()
un singleton mais ce n'est pas de forme fixe. Mais ceWeird
n'est pas unFunctor
, donc ça ne peut pas être deStrongApplicative
toute façon. Je suppose que la conjecture pertinente serait: sif
est unFunctor
,f ()
est-ce qu'un singleton impliquef
une forme fixe ? Je soupçonne fortement que cela est vrai, mais comme vous l'avez noté, je n'ai pas encore de preuve.Nous pouvons répondre à au moins une de ces questions par la négative:
En fait, l'un des exemples de licite
StrongApplicative
dans la question initiale est erroné. L'écrivainMonoid => (,) m
n'est pas applicatifStrongApplicative
, car par exemplehusk $ unhusk $ ("foo", ()) == ("", ()) /= ("foo", ())
.De même, l'exemple d'un conteneur de taille fixe:
de multiplicité fixe 1, n'est pas un fort applicatif, car si l'on définit
husk = Left
alorshusk $ unhusk $ Right () /= Right ()
, et vice versa. Une façon équivalente de voir cela est que ce n'est que le rédacteur applicatif pour votre choix de monoïdeBool
.Il existe donc des applications "à taille fixe" qui ne le sont pas
StrongApplicative
.StrongApplicative
Reste à savoir si tous les s sont de taille fixe.la source
Prenons les foncteurs représentables comme notre définition de "conteneur de taille fixe":
Le réel
Representable
a quelques lois et superclasses, mais pour les besoins de cette réponse, nous avons en fait besoin de seulement deux propriétés:(D'accord, nous avons également besoin d'un respectueux des lois
instance StrongApplicative ((->) r)
. Easy peasy, vous êtes déjà d'accord pour dire qu'il existe.)Si nous prenons cette définition, je peux confirmer cette conjecture 1:
est vrai. Voici comment:
Il y a beaucoup de lois à prouver, mais je me concentrerai uniquement sur les quatre grands qui
StrongApplicative
ajoutent - vous croyez probablement déjà les premières pourApplicative
etOpApplicative
, mais si vous ne le faites pas, leurs preuves ressemblent à celles ci-dessous ( qui à leur tour se ressemblent beaucoup). Pour plus de clarté, je vais utiliserzipf
,huskf
etc. pour l'instance de fonction, etzipr
,huskr
etc. pour l'instance représentable, de sorte que vous pouvez garder une trace de ce qui est qui. (Et pour qu'il soit facile de vérifier que nous ne prenons pas la chose que nous essayons de prouver comme une hypothèse! C'est correct de l'utiliserunhuskf . huskf = id
pour prouverunhuskr . huskr = id
, mais ce serait une erreur de supposerunhuskr . huskr = id
dans cette même preuve.)La preuve de chaque loi procède essentiellement de la même manière: déroulez les définitions, supprimez l'isomorphisme qui
Representable
vous donne, puis utilisez la loi analogue pour les fonctions.la source
instance StrongApplicative f => Representable f where type Rep f = forall x. f x -> x
.index
est facile. Je n'ai pas encore trouvé le truc pour letabulate
moment, mais cela semble terriblement proche.StrongApplicative
instance également, mais je n'ai pas pu prouver les lois. Félicitations pour avoir compris! J'ai essayé de faire l'Representable
instance aussi bienStrongApplicative
, mais je ne pouvais pas penser à un bonRep
type - je serais curieux de savoir comment vous yforall x. f x -> x
arrivez?forall x. f x -> x
sont exactement les fonctions qui choisissent un trou et renvoient la valeur dans ce trou. (Et, en réfléchissant à la façon de mettre en œuvretabulate
, j'ai soulevé une objection au type pourunhusk
; voir les commentaires sur la question elle-même pour plus de détails.)forall x. f x -> x
cela fonctionnera commeRep
. Mon raisonnement est que, en utilisant celaRep
, vous pouvez écrireindex
pour n'importe quel type, pas seulementStrongApplicative
ceux - donc je soupçonne que celaforall x. f x -> x
pourrait être trop général.