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; OpApplicative
ist keine spezifischere Einschränkung als Functor
:
instance Functor f => OpApplicative f
where
unzip fab = (fst <$> fab, snd <$> fab)
unhusk = const ()
Obwohl jeder Applicative
Funktor (wirklich jeder Functor
) trivial ist OpApplicative
, gibt es nicht unbedingt eine schöne Beziehung zwischen den Applicative
Nachlässigkeiten und OpApplicative
Oplaxitä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 Applicative
eine rechtmäßige Instanz dieser Klasse.
Hier sind einige Applicative
Funktoren, für die wir rechtmäßige Fälle deklarieren können StrongApplicative
:
Identity
VoidF
(->) r
(siehe Antworten)Monoid m => (,) m
Vec (n :: Nat)
Stream
(unendlich)
Und hier sind einige Applicative
s, für die wir nicht können:
[]
Either e
Maybe
NonEmptyList
Das Muster hier legt nahe, dass die StrongApplicative
Klasse in gewissem Sinne die FixedSize
Klasse ist, wobei "feste Größe" * bedeutet, dass die Vielzahl ** der Einwohner a
eines Bewohners vonf a
festgelegt ist.
Dies kann als zwei Vermutungen angegeben werden:
- Jeder,
Applicative
der einen Container "fester Größe" von Elementen seines Typarguments darstellt, ist eine Instanz vonStrongApplicative
- Es
StrongApplicative
gibt keine Instanz von , in der die Anzahl der Vorkommena
variieren 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. StrongApplicative
ist 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 StrongApplicative
Instanz 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
(->) r
sie 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 Representable
Typklasse in Haskell ein mysteriöses tabulate . return = return
Gesetz (das für nicht-monadische Funktoren nicht wirklich Sinn macht), aber es gibt uns 1/4 der Bedingungen, die wir dazu sagen müssen, tabulate
und zip
sind Morphismen einer geeigneten Kategorie von Monoiden . Die anderen 3 sind zusätzliche Gesetze, die Sie fordern müssen.
tabulate
und index
sind Morphismen einer geeigneten Kategorie ..." sein
return
kein ernstes Problem ist. cotraverse getConst . Const
ist eine Standardimplementierung für return
/ pure
in Bezug auf Distributive
und da Distributives / Representables eine feste Form haben, ist diese Implementierung eindeutig.