Es wurde behauptet, dass newtype T a = T (a -> Int)
ein Typkonstruktor ist, der kein Funktor ist (aber ein kontravarianter Funktor). Wie das? Oder was ist der kontravariante Funktor (woher nehme ich an, dass es offensichtlich ist, warum dies nicht zu einem normalen Funktor gemacht werden kann)?
Gegeben
%Vor% Versuchen wir, die Contravariant
Instanz für diesen Datentyp zu erstellen.
Hier ist die Typklasse in Frage:
%Vor% Grundsätzlich ist contramap
ähnlich wie fmap
, aber anstatt eine Funktion a -> b
auf f a -> f b
zu heben, wird sie auf f b -> f a
angehoben.
Beginnen wir mit dem Schreiben der Instanz ...
%Vor% Bevor wir ?
ausfüllen, überlegen wir, was die Typen g
und f
sind:
Und aus Gründen der Klarheit könnten wir das auch erwähnen
%Vor% So können wir das ?
wie folgt ausfüllen:
Um super pedantisch zu sein, könnten Sie g
als aToB
und f
als bToInt
umbenennen.
Der Grund, warum Sie eine Contravariant
-Instanz für T a
schreiben können, ist die Tatsache, dass a
in kontravarianter Position in T (a -> Int)
steht. Der beste Weg, um sich davon zu überzeugen, dass T a
nicht Functor
ist, ist es, die Functor
-Instanz selbst zu schreiben (und nicht zu schreiben).
Angenommen, T ist ein Funktor. Dann haben wir
%Vor%Nun versuchen wir es zu implementieren.
%Vor% Wir fangen eindeutig mit so etwas an und kombinieren die Werte f
, g
und x
irgendwie, um einen Wert für y
vom richtigen Typ zu berechnen. Wie können wir das machen? Nun, beachte, dass ich es _y
genannt habe, was GHC sagt, dass ich Hilfe brauche, um herauszufinden, was ich dort hinlege. Was hat GHC zu sagen?
Also jetzt sind wir uns über die Arten von allem relevanten klar, oder? Wir müssen irgendwie ein Int
zurückgeben, und wir müssen es aufbauen:
Nun, okay, das einzige, was wir haben können, ist möglicherweise Int
ist g
, also lassen Sie uns das ausfüllen, lassen Sie g
's Argument leer, um GHC nach mehr Hilfe zu fragen:
Okay, wir hätten das selbst vorhersagen können: Um g
zu nennen, brauchen wir irgendwo einen Wert vom Typ a
. Aber wir haben keine Werte vom Typ a
im Bereich, und wir haben auch keine Funktionen, die einen Wert vom Typ a
ausgeben! Wir stecken fest: Es ist unmöglich, einen Wert des Typs zu produzieren, den wir jetzt wollen, obwohl wir bei jedem Schritt das einzig mögliche getan haben: Es gibt nichts, was wir sichern und anders ausprobieren könnten.
Warum ist das passiert? Denn wenn ich dir eine Funktion vom Typ a -> Int
gebe und sag "aber nebenbei, hier ist eine Funktion von a -> b
, gib mir bitte eine Funktion von b -> Int
zurück", kannst du eigentlich nicht verwenden die Funktion von a -> b
, denn niemand gibt dir jemals a
s, um sie aufzurufen! Wenn ich dir stattdessen eine Funktion von b -> a
gegeben hätte, wäre das sehr hilfreich, oder? Sie könnten dann eine Funktion von b -> Int
erzeugen, indem Sie zuerst die Funktion b -> a
aufrufen, um eine a
zu erhalten, und dann die ursprüngliche Funktion a -> Int
aufrufen, um die gewünschte Int
herauszuholen.
Und darum geht es bei einem kontravarianten Funktor: Wir kehren den Pfeil in der an fmap
übergebenen Funktion um, damit er über Dinge, die Sie "brauchen" (Funktionsargumente), statt über Dinge, die Sie "haben" (konkrete Werte) Rückgabewerte von Funktionen usw.).
Nebenbei: Ich behauptete früher, dass wir "die einzig mögliche Sache" bei jedem Schritt getan hatten, was ein bisschen eine Frechheit war. Wir können keine Int
aus f
, g
und x
erstellen, aber natürlich können wir alle möglichen Zahlen aus der Luft zusammensetzen. Wir wissen nichts über den Typ b
, also können wir nicht eine Int
erhalten, die von dieser in irgendeiner Weise abgeleitet ist, aber wir könnten einfach sagen "lasst uns immer Null zurückgeben", und dies erfüllt technisch die Typüberprüfung :
Offensichtlich sieht das ziemlich falsch aus, da es scheint, dass f
und g
ziemlich wichtig sein sollten und wir ignorieren sie! Aber es prüft, also sind wir in Ordnung, oder?
Nein, das verletzt eines der Functor-Gesetze:
%Vor%Wir können das leicht genug beweisen:
%Vor% Und jetzt haben wir wirklich alles versucht: Die einzige Möglichkeit, ein Int
zu erstellen, ohne unseren b
-Typ überhaupt zu benutzen, ist, es aus nichts zu machen, und so weiter Verwendungen werden isomorph zur Verwendung von const
sein, was gegen die Functor-Gesetze verstößt.
Hier ist noch ein bisschen Perspektive. Wie liminish zeigt, ist T
Contravariant
. Was können wir über Typen sagen, die sowohl kovariant als auch kontravariant sind?
Die ersten beiden Implementierungen sind im Grunde gleich ( change1'
ist eine Optimierung von change1
); jeder von ihnen verwendet die Tatsache, dass ()
ein "terminales Objekt" von Hask ist. change2
verwendet stattdessen die Tatsache, dass Void
ein "initiales Objekt" ist.
Jede dieser Funktionen ersetzt alle a
s in f a
mit b
s, ohne etwas über a
, b
oder die Beziehung zwischen ihnen zu wissen, so dass alles andere gleich bleibt. Es sollte wohl klar sein, dass f a
nicht wirklich von a
abhängt. Das heißt, der Parameter f
muss ein Phantom sein. Das ist nicht der Fall für T
, daher kann es nicht auch kovariant sein.
Tags und Links haskell category-theory