Das Applicative Typklasse repräsentiert laxe monoidale Funktoren, die die kartesische monoidale Struktur in der Kategorie der typisierten Funktionen beibehalten.
Mit anderen Worten, angesichts der kanonischen Isomorphismen, (,)die eine monoidale Struktur bilden:
-- 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, ())
Die Typklasse und ihre Gesetze können äquivalent so geschrieben werden:
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
Man könnte sich fragen, wie ein Funktor aussehen könnte, der in Bezug auf dieselbe Struktur oplax monoidal ist:
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
Wenn wir über die Typen nachdenken, die an den Definitionen und Gesetzen beteiligt sind, wird die enttäuschende Wahrheit offenbart; OpApplicativeist keine spezifischere Einschränkung als Functor:
instance Functor f => OpApplicative f
where
unzip fab = (fst <$> fab, snd <$> fab)
unhusk = const ()
Obwohl jeder ApplicativeFunktor (wirklich jeder Functor) trivial ist OpApplicative, gibt es nicht unbedingt eine schöne Beziehung zwischen den ApplicativeNachlässigkeiten und OpApplicativeOplaxitäten. So können wir nach starken monoidalen Funktoren für die kartesische monoidale Struktur suchen :
class (Applicative f, OpApplicative f) => StrongApplicative f
-- Laws:
-- unhusk . husk = id
-- husk . unhusk = id
-- zip . unzip = id
-- unzip . zip = id
Das erste Gesetz oben ist trivial, da der einzige Bewohner des Typs () -> ()die Identitätsfunktion ist ().
Die verbleibenden drei Gesetze und damit die Unterklasse selbst sind jedoch nicht trivial. Insbesondere ist nicht jeder Applicativeeine rechtmäßige Instanz dieser Klasse.
Hier sind einige ApplicativeFunktoren, für die wir rechtmäßige Fälle deklarieren können StrongApplicative:
IdentityVoidF(->) r(siehe Antworten)Monoid m => (,) mVec (n :: Nat)Stream(unendlich)
Und hier sind einige Applicatives, für die wir nicht können:
[]Either eMaybeNonEmptyList
Das Muster hier legt nahe, dass die StrongApplicativeKlasse in gewissem Sinne die FixedSizeKlasse ist, wobei "feste Größe" * bedeutet, dass die Vielzahl ** der Einwohner aeines Bewohners vonf a festgelegt ist.
Dies kann als zwei Vermutungen angegeben werden:
- Jeder,
Applicativeder einen Container "fester Größe" von Elementen seines Typarguments darstellt, ist eine Instanz vonStrongApplicative - Es
StrongApplicativegibt keine Instanz von , in der die Anzahl der Vorkommenavariieren kann
Kann sich jemand Gegenbeispiele vorstellen, die diese Vermutungen widerlegen, oder überzeugende Argumente, die zeigen, warum sie wahr oder falsch sind?
* Mir ist klar, dass ich das Adjektiv "feste Größe" nicht richtig definiert habe. Leider ist die Aufgabe etwas kreisförmig. Ich kenne keine formale Beschreibung eines Containers mit "fester Größe" und versuche, einen zu finden. StrongApplicativeist mein bisher bester Versuch.
Um zu beurteilen, ob dies eine gute Definition ist, brauche ich etwas, mit dem ich sie vergleichen kann. Angesichts einer formellen / informellen Definition dessen, was es für einen Funktor bedeutet, eine bestimmte Größe oder Vielfalt in Bezug auf Einwohner seines Typarguments zu haben, stellt sich die Frage, ob die Existenz einer StrongApplicativeInstanz Funktoren fester und unterschiedlicher Größe genau unterscheidet.
Da mir eine bestehende formale Definition nicht bekannt ist, appelliere ich an die Intuition, wenn ich den Begriff "feste Größe" verwende. Allerdings, wenn jemand bereits einen bestehenden Formalismus für die Größe eines Funktors kennt und vergleichen kannStrongApplicative besser.
** Mit "Multiplizität" beziehe ich mich in einem losen Sinne auf "wie viele" beliebige Elemente des Parametertyps des Funktors in einem Bewohner des Codomänen-Typs des Funktors vorkommen. Dies ist ohne Rücksicht auf den speziellen Typ der Funktor auf, angewendet wird und daher ohne Rücksicht auf irgendwelche spezifischen Bewohner des Parametertyps.
Nicht genau zu sein, hat einige Verwirrung in den Kommentaren verursacht. Hier sind einige Beispiele dafür, was ich für die Größe / Vielzahl verschiedener Funktoren halten würde:
VoidF: fest, 0Identity: fest, 1Maybe: variabel, Minimum 0, Maximum 1[]: variabel, Minimum 0, Maximum unendlichNonEmptyList: variabel, Minimum 1, Maximum unendlichStream: fest, unendlichMonoid m => (,) m: fest, 1data Pair a = Pair a a: fest, 2Either x: variabel, Minimum 0, Maximum 1data Strange a = L a | R a: fest, 1
(->) rsie auf die richtige Weise isomorph sind.
(->) r; Sie benötigen die Komponenten des Isomorphismus, um die starke Anwendungsstruktur zu erhalten. Aus irgendeinem Grund hat die RepresentableTypklasse in Haskell ein mysteriöses tabulate . return = returnGesetz (das für nicht-monadische Funktoren nicht wirklich Sinn macht), aber es gibt uns 1/4 der Bedingungen, die wir dazu sagen müssen, tabulateund zipsind Morphismen einer geeigneten Kategorie von Monoiden . Die anderen 3 sind zusätzliche Gesetze, die Sie fordern müssen.
tabulateund indexsind Morphismen einer geeigneten Kategorie ..." sein
returnkein ernstes Problem ist. cotraverse getConst . Constist eine Standardimplementierung für return/ purein Bezug auf Distributiveund da Distributives / Representables eine feste Form haben, ist diese Implementierung eindeutig.