2014-12-11 11 views
6

Ho appena imparato Scala. Ora sono confuso riguardo la controvarianza e la covarianza.Contravarianza vs Covarianza in Scala

Da questo page, ho imparato qualcosa di seguito:

covarianza

Forse la caratteristica più evidente di sottotipo è la possibilità di sostituire un valore di un tipo più ampio con un valore di un tipo più stretta in un'espressione . Ad esempio, supponiamo di avere alcuni tipi Real, Integer <: Real e alcuni tipi non collegati Boolean. Posso definire una funzione is_positive :: Real -> Boolean che funziona sui valori Real, ma posso anche applicare questa funzione ai valori di tipo Integer (o qualsiasi altro sottotipo di Real). Questa sostituzione di tipi più ampi (antenati) con tipi più stretti (discendenti) è denominata covariance. Il concetto di covariance ci consente di scrivere codice generico ed è inestimabile quando ragioniamo sull'ereditarietà nei linguaggi di programmazione orientata agli oggetti e sul polimorfismo nei linguaggi funzionali.

Tuttavia, ho anche visto qualcosa da qualche altra parte:

scala> class Animal
 defined class Animal 

scala> class Dog extends Animal
 defined class Dog 

scala> class Beagle extends Dog
 defined class Beagle 

scala> def foo(x: List[Dog]) = x
 foo: (x: List[Dog])List[Dog] // Given a List[Dog], just returns it
  

scala> val an: List[Animal] = foo(List(new Beagle))
 an: List[Animal] = List([email protected]) 

Parametro x di foo è contravariant; si aspetta un argomento di tipo List[Dog], ma gli diamo un List[Beagle], e va bene

[Quello che penso sia il secondo esempio dovrebbe anche provare Covariance. Perché dal primo esempio ho appreso che "applica questa funzione ai valori di tipo Integer (o qualsiasi altro sottotipo di Real)". Quindi, corrispondentemente, qui si applica questa funzione ai valori di tipo List[Beagle] (o qualsiasi altro sottotipo di List[Dog]). Ma con mia sorpresa, il secondo esempio dimostra Cotravariance]

Penso che due stiano parlando la stessa cosa, ma uno dimostra Covariance e l'altro Contravariance. Ho anche visto this question from SO. Tuttavia sono ancora confuso. Ho perso qualcosa o uno degli esempi è sbagliato?

risposta

9

che è possibile passare un List[Beagle] a una funzione in attesa di un List[Dog] è niente a che fare con controvarianza di funzioni, è ancora, perché lista è covariante e che List[Beagle] è un List[Dog].

permette invece dire che si ha una funzione:

def countDogsLegs(dogs: List[Dog], legCountFunction: Dog => Int): Int 

Questa funzione conta tutte le gambe in una lista di cani. Prende una funzione che accetta un cane e restituisce un int che rappresenta quante gambe ha questo cane.

consente inoltre dire che abbiamo una funzione:

def countLegsOfAnyAnimal(a: Animal): Int 

che può contare le gambe di qualsiasi animale. Possiamo passare la nostra funzione alla nostra funzione countDogsLegs come argomento della funzione, questo perché se questa cosa può contare le zampe di qualsiasi animale, può contare le zampe dei cani, perché i cani sono animali, questo perché le funzioni sono controvarianti.

Se si guarda la definizione di Function1 (funzioni di un parametro), è

trait Function1[-A, +B] 

Che è che sono contravariant sul loro input e covariante sulla loro uscita. Quindi, dal momento che Function1[Animal,Int] <: Function1[Dog,Int]Dog <: Animal

+0

Buona spiegazione. Il tipo di ritorno di 'groomAnyAnimal' dovrebbe essere' Dog' per collegarlo, dato che le funzioni sono covarianti nel loro tipo di ritorno e controvarianti solo nel loro tipo di argomento. –

+0

@stew quindi pensi che le dichiarazioni del secondo esempio siano in qualche modo sbagliate? – CSnerd

+1

@CSnerd Nel tuo secondo esempio, in qualche modo vuoi fare una dichiarazione sul fatto che 'x' sia covariante o controverso. Tuttavia, come menzionato lo stufato, la varianza non ha nulla a che fare se si riesce a passare un sottotipo in cui è richiesto un supertipo (si veda [Principio di sostituzione di Liskov] (http://en.wikipedia.org/wiki/Liskov_substitution_principle)). L'unica cosa covariante nel tuo secondo esempio è 'List', quindi' List [Beagle] 'è un sottotipo di' List [Dog] '. –

6

Un articolo recente Buono (agosto 2016) su questo argomento è "Cheat Codes for Contravariance and Covariance" di Matt Handler.

Si parte dal concetto generale come presentato in "Covariance and Contravariance of Hosts and Visitors" e lo schema da Andre Tyukin e s' answeranoopelias.

http://blog.originate.com/images/variance.png

E la sua si conclude con:

Ecco come determinare se il vostro type ParametricType[T] può/non può essere covariante/contravariant:

  • Un tipo può essere covariante quando non chiama i metodi sul tipo che è generico su.
    Se il tipo deve chiamare metodi su oggetti generici passati, non può essere covariante.

esempi Archetipici:

Seq[+A], Option[+A], Future[+T] 
  • Un tipo può essere contravariant quando lo fa chiamare metodi del tipo che è generico su.
    Se il tipo deve restituire i valori del tipo su cui è generico, non può essere controverso.

esempi archetipiche:

`Function1[-T1, +R]`, `CanBuildFrom[-From, -Elem, +To]`, `OutputChannel[-Msg]` 

Per quanto riguarda controvarianza,

funzioni sono il miglior esempio di controvarianza
(si noti che sono solo contravariant sulla loro argomenti e in realtà sono covariante sul loro risultato).
Per esempio:

class Dachshund(
    name: String, 
    likesFrisbees: Boolean, 
    val weinerness: Double 
) extends Dog(name, likesFrisbees) 

def soundCuteness(animal: Animal): Double = 
    -4.0/animal.sound.length 

def weinerosity(dachshund: Dachshund): Double = 
    dachshund.weinerness * 100.0 

def isDogCuteEnough(dog: Dog, f: Dog => Double): Boolean = 
    f(dog) >= 0.5 

Dovremmo essere in grado di passare weinerosity come argomento a isDogCuteEnough?La risposta è no, perché la funzione isDogCuteEnough garantisce solo che possa passare, al più specifico, un Dog alla funzione f.
Quando la funzione f si aspetta qualcosa di più specifico di quello che isDogCuteEnough in grado di fornire, si potrebbe tentare di chiamare un metodo che alcuni Dogs non hanno (come .weinerness su un Greyhound, che è folle).

0

varianza è usato per indicare subtyping in termini di Contenitori (es: List). Nella maggior parte delle lingue, se una funzione richiede l'oggetto di Class Animal, passare qualsiasi classe che eredita lo Animal (ad esempio: Dog) sarà valido. Tuttavia, in termini di contenitori, questi non devono essere validi. Se la tua funzione vuole Container[A], quali sono i possibili valori che possono essere passati ad esso? Se B si estende A e passando Container[B] è valido, quindi è Covariant (ad esempio: List[+T]). Se, A estende B (il caso inverso) e passa Container[B] per Container[A] è valido, quindi è controvariante. Altrimenti, è invariante (che è l'impostazione predefinita). È possibile fare riferimento a un articolo in cui ho provato a spiegare le variazioni in Scala https://lakshmirajagopalan.github.io/variance-in-scala/.