La Applicativeclasse 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; OpApplicativen'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 Applicativefoncteur (vraiment, n'importe lequel Functor) est trivialement OpApplicative, il n'y a pas nécessairement une bonne relation entre les Applicativelaxités et les OpApplicativeoplaxité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 Applicativeune instance légale de cette classe.
Voici quelques Applicativefoncteurs pour lesquels nous pouvons déclarer des instances licites de StrongApplicative:
IdentityVoidF(->) r(voir les réponses)Monoid m => (,) mVec (n :: Nat)Stream(infini)
Et voici quelques Applicatives pour lesquels nous ne pouvons pas:
[]Either eMaybeNonEmptyList
Le schéma suggère ici que la StrongApplicativeclasse est en quelque sorte la FixedSizeclasse, où "taille fixe" * signifie que la multiplicité ** d'habitants d' aun habitant de f aest fixe.
Cela peut être défini comme deux conjectures:
- Chaque
Applicativereprésentant un conteneur "de taille fixe" d'éléments de son argument type est une instance deStrongApplicative - Aucun cas de
StrongApplicativeexiste dans lequel le nombre d'occurrences deapeut 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. StrongApplicativeest 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 StrongApplicativeinstance 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
(->) rils 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, la Representableclasse de caractères de Haskell a une tabulate . return = returnloi 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 dire tabulateet zipsont des morphismes d'une catégorie appropriée de monoïdes . Les 3 autres sont des lois supplémentaires que vous devez exiger.
tabulateet indexsont des morphismes d'une catégorie appropriée ..."
returnn'est pas un problème grave. cotraverse getConst . Constest une implémentation par défaut pour return/ pureen termes de Distributive, et, comme les distributifs / représentables ont une forme fixe, cette implémentation est unique.