Parliamo di varianze.
Ecco la nozione di base. Si consideri il tipo A -> B
. Quello che voglio che tu possa immaginare è che un simile tipo è simile a "avere un B
" e anche "a causa di un A
".Infatti, se restituisci il tuo A
ricevi immediatamente il tuo B
. Le funzioni sono un po 'come l'impegno in questo modo.
La nozione di "avere" e "dovere" può estendersi ad altri tipi. Per esempio, il contenitore più semplice
newtype Box a = Box a
si comporta in questo modo: se si "ha" un Box a
allora anche voi "avere" un a
. Consideriamo i tipi che hanno tipo * -> *
e "abbiamo" la loro tesi di essere (covarianti) funtori e possiamo istanziare a Functor
instance Functor Box where fmap f (Box a) = Box (f a)
Che cosa succede se si considera il tipo di predicati oltre un tipo, come
newtype Pred a = Pred (a -> Bool)
in questo caso, se "abbiamo" un Pred a
, in realtà "dobbiamo" un a
. Questo deriva dal a
che si trova sul lato sinistro della freccia (->)
. Dovedi Functor
viene definito passando la funzione nel contenitore e applicandola a tutti i luoghi in cui "abbiamo" il nostro tipo interno, non possiamo fare lo stesso per Pred a
poiché non "abbiamo" e a
s.
Invece, lo faremo
class Contravariant f where
contramap :: (a -> b) -> (f b -> f a)
Ora che contramap
è come un "capovolto" fmap
? Ci consentirà di applicare la funzione ai luoghi in cui "possediamo" uno b
in Pred b
per ricevere uno Pred a
. Potremmo chiamare contramap
"baratto" perché codifica l'idea che se si sa come ottenere b
s da a
s, è possibile convertire un debito di b
s in un debito di a
s.
Vediamo come funziona
instance Contravariant Pred where
contramap f (Pred p) = Pred (\a -> p (f a))
abbiamo appena eseguire il nostro commercio con f
prima di trasmetterla alla funzione predicato. Meraviglioso!
Quindi ora abbiamo tipi covarianti e controvarianti. Tecnicamente, questi sono conosciuti come "funtori" covarianti e controvarianti. Dirò anche immediatamente che quasi sempre un funzionario controverso non è anche covariante. Questo, quindi, risponde alla tua domanda: esiste un gruppo di funtori controvarianti che non possono essere istanziati a Functor
. Pred
è uno di questi.
Ci sono tipi complicati che sono sia funtori controvarianti che covarianti. In particolare, i funtori costanti:
data Z a = Z -- phantom a!
instance Functor Z where fmap _ Z = Z
instance Contravariant Z where contramap _ Z = Z
In realtà, si può in sostanza, dimostrare che tutto ciò che è sia Contravariant
e Functor
ha un parametro fantasma.
isPhantom :: (Functor f, Contravariant f) => f a -> f b -- coerce?!
isPhantom = contramap (const()) . fmap (const()) -- not really...
D'altra parte, ciò che accade con un tipo come
-- from Data.Monoid
newtype Endo a = Endo (a -> a)
In Endo a
entrambi dobbiamo e ricevere una a
.Significa che siamo senza debiti? Bene, no, significa solo che Endo
vuole essere sia covariante che controvariante e non ha un parametro fantasma. Il risultato: Endo
è invariante e non è possibile creare un'istanza né Functor
né Contravariant
.
Un functor 'f' ha' fmap :: (a -> b) -> f a -> f b', che soddisfa 'fmap x. fmap y = fmap (x. y) '. Vieni con un tipo in cui (1) non puoi definire 'fmap', o dove (2) puoi definire' fmap' ma non puoi farlo seguire la regola. –
Suggerimento: un tipo comune di functor è un contenitore, quindi se inventi un nuovo tipo di contenitore probabilmente sarà un funtore. Prova a creare un tipo 'X a' tale che' X a' non contenga un 'a', ma forse fa qualcos'altro con' a'. –
Aggiungo al suggerimento di @ DietrichEpp ricordandovi che Haskell è un linguaggio _function_-al. Ti da qualche idea? – bheklilr