Lassen Sie uns über Varianzen sprechen.
Hier ist die Grundidee. Betrachten Sie den Typ A -> B
. Ich möchte, dass Sie sich vorstellen, dass ein solcher Typ ähnlich ist wie "mit einem B
" und auch "mit einem A
".In der Tat, wenn Sie Ihre A
zurückzahlen, erhalten Sie sofort Ihre B
. Funktionen sind auf diese Art wie Escrow.
Der Begriff "Haben" und "Schuld" kann sich auf andere Typen erstrecken. Zum Beispiel ist die einfachste Behälter
newtype Box a = Box a
verhält sich wie folgt: Wenn Sie „haben“ ein Box a
dann „haben“ Sie auch eine a
. Wir Arten betrachten, welche Art * -> *
haben und „haben“ ihr Argument (kovarianten) functors zu sein, und wir können sie
instance Functor Box where fmap f (Box a) = Box (f a)
zu Functor
instanziiert Was passiert, wenn wir die Art von Prädikaten über eine Art betrachten, wie
newtype Pred a = Pred (a -> Bool)
in diesem Fall, wenn wir eine Pred a
haben, "schulden" wir eigentlich eine a
. Dies ergibt sich aus der a
auf der linken Seite des Pfeils (->)
. Wo fmap
von Functor
definiert wird, indem die Funktion in den Container übergeben wird und auf alle Orte angewendet wird, an denen wir unseren inneren Typ "haben", können wir nicht dasselbe für Pred a
tun, da wir nicht "haben" und a
s.
Stattdessen werden wir dies tun
class Contravariant f where
contramap :: (a -> b) -> (f b -> f a)
Nun, da contramap
ist wie ein "umgedreht" fmap
? Es erlaubt uns, die Funktion auf die Orte anzuwenden, an denen wir eine b
in Pred b
"besitzen", um eine Pred a
zu erhalten. Wir könnten contramap
"Tauschhandel" nennen, weil es die Idee codiert, dass, wenn Sie wissen, wie man b
s von a
s bekommt, dann können Sie eine Schuld von b
s in eine Schuld von a
s verwandeln.
Mal sehen, wie es funktioniert
instance Contravariant Pred where
contramap f (Pred p) = Pred (\a -> p (f a))
wir unsere Handels laufen nur f
mit bevor sie auf das Bestehen der Prädikatfunktion auf in. Wunderbar!
So jetzt haben wir kovariante und kontravariante Typen. Technisch sind diese als kovariante und kontravariante "Funktoren" bekannt. Ich werde auch sofort feststellen, dass fast immer ein kontravarianter Funktor nicht auch kovariant ist. Dies beantwortet also Ihre Frage: Es existiert eine Reihe kontravarianter Funktoren, die nicht in die Instanz Functor
instanziiert werden können. Pred
ist einer von ihnen.
Es gibt knifflige Typen, die sowohl kontravariante als auch kovariante Funktoren sind. Insbesondere die konstanten functors:
data Z a = Z -- phantom a!
instance Functor Z where fmap _ Z = Z
instance Contravariant Z where contramap _ Z = Z
In der Tat können Sie im Wesentlichen, dass alles beweisen, die sowohl Contravariant
und Functor
einen Phantom Parameter haben.
isPhantom :: (Functor f, Contravariant f) => f a -> f b -- coerce?!
isPhantom = contramap (const()) . fmap (const()) -- not really...
Auf der anderen Seite, was mit einer Art geschieht, wie
-- from Data.Monoid
newtype Endo a = Endo (a -> a)
In Endo a
wir beide verdanken und eine a
erhalten.Bedeutet das, dass wir schuldenfrei sind? Nun, nein, es bedeutet nur, dass Endo
sowohl kovariant als auch kontravariant sein will und hat keinen Phantomparameter. Das Ergebnis: Endo
ist Invariante und kann weder Functor
noch Contravariant
instanziieren.
Ein Funktor 'f' hat' fmap :: (a -> b) -> f a -> f b ', was 'fmap x' erfüllt. fmap y = fmap (x. y) '. Stellen Sie sich einen Typ vor, bei dem (1) Sie 'fmap' nicht definieren können oder wo (2) Sie' fmap' definieren können, aber nicht der Regel folgen können. –
Hinweis: Ein üblicher Funktor ist ein Container, also wenn Sie eine neue Art von Container erfinden, wird es wahrscheinlich ein Funktor sein. Versuchen Sie, einen Typ 'X a' zu erstellen, so dass' X a' kein 'a' enthält, aber vielleicht etwas anderes mit' a'. –
Ich werde den Hinweis von @ DietrichEpp hinzufügen, indem ich Sie daran erinnere, dass Haskell eine _funktionale_ Sprache ist. Gib dir irgendwelche Ideen? – bheklilr