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
(->) 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, la Representable
classe de caractères de Haskell a une tabulate . 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 dire tabulate
et zip
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
et index
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 pour return
/ pure
en termes de Distributive
, et, comme les distributifs / représentables ont une forme fixe, cette implémentation est unique.