2016-10-28 3 views
2
abstract class Bhanu[-A] { val m:List[A] } 

donneContravariance et covariance Scala

error: contravariant type A occurs in covariant position in type => List[A] of value m 
     abstract class Bhanu[-A] { val m:List[A] } 

alors

abstract class Bhanu[+A] { val m:List[A] } 

donne

defined class Bhanu 

Je ne suis pas en mesure d'envelopper ma tête autour de ce concept pour expliquer pourquoi il échoue pour contravariance alors qu'il réussit pour co variance.

En second lieu (d'un autre exemple),

Qu'est-ce que la déclaration signifie exactement?

Function1[Sport,Int] <: Function1[Tennis,Int] since Tennis <: Sport 

Cela me semble contre-intuitif, ne devrait-il pas être le suivant?

Function1[Tennis,Int] <: Function1[Sport,Int] since Tennis <: Sport 

Répondre

5

Regardons le premier exemple que vous avez mentionné. Tenez compte, nous avons:

class Fruit 
class Apple extends Fruit 
class Banana extends Fruit 

class Bhanu[-A](val m: List[A]) // abstract removed for simplicity 

Depuis Bhanu est contravatiant Bhanu[Fruit] <: Bhanu[Apple] afin que vous puissiez effectuer les opérations suivantes:

val listOfApples = new List[Apple](...) 
val listOfFruits = listOfApples // Since Lists are covariant in Scala 
val a: Bhanu[Fruit] = new Bhanu[Fruit](listOfFruits) 
val b: Bhanu[Banana] = a // Since we assume Bhanu is contravariant 
val listOfBananas: List[Banana] = b.m 
val banana: Banana = listOfBananas(0) // TYPE ERROR! Here object of type Banana is initialized 
             // with object of type Apple 

Alors compilateur Scala nous protège de telles erreurs par restriction d'utiliser des paramètres de type contravariants en position covariante.

Pour votre deuxième question, regardons aussi l'exemple. Tenez compte que nous avons la fonction:

val func1: Function1[Tennis,Int] = ... 

Si Function1[Tennis,Int] <: Function1[Sport,Int]Tennis <: Sport que vous proposiez que nous pouvons faire ce qui suit:

val func2: Function1[Sport,Int] = func1 
val result: Int = func2(new Swimming(...)) // TYPE ERROR! Since func1 has argument 
              // of type Tennis. 

Mais si nous faisons Function1 contravariant dans son argument si Function1[Sport,Int] <: Function1[Tennis,Int]Tennis <: Sport que:

val func1: Function1[Tennis,Int] = ... 
val func2: Function1[Sport,Int] = func1 // COMPILE TIME ERROR! 

et tout va bien pour le cas inverse:

val func1: Function1[Sport,Int] = ... 
val func2: Function1[Tennis,Int] = func1 // OK! 
val result1: Int = func1(new Tennis(...)) // OK! 
val result2: Int = func2(new Tennis(...)) // OK! 

Les fonctions doivent être contravariants dans leur type d'argument et covariant dans le type de résultat:

trait Function1[-T, +U] { 
    def apply(x: T): U 
} 
+0

Je pense que vous vouliez dire 'Apple étend Fruit' au lieu de' Base'. –

+0

@ Yuval fixe, merci. – dkolmakov

4

dkolmakov's answer-t un bon travail expliquant pourquoi cet exemple ne fonctionnera pas. Peut-être qu'une explication plus générale aiderait aussi.

Que signifie pour un constructeur de type, une fonction ou un trait d'être de variance?Selon le definition on Wikipedia:

Au sein du système de type d'un langage de programmation, une règle de frappe ou d'un constructeur de type est:

  • Covariant: si elle préserve l'ordre des types (≤, qui commande des types plus spécifiques à plus génériques; Contravariant: si inverse cette commande;

  • Invariant ou non variable si aucune de ces conditions ne s'applique.

Maintenant, ce qui est un ordre sur les types? et qu'est-ce que cela signifie dans le monde pour préserver ou inverser l'ordre? Cela signifie que pour tout type T et U, il soit existe une relation où:

  • Covariance: T <: U ->M[T] <: M[U] - Par exemple, un Cat <: Animal, si List[Cat] <: List[Animal]
  • Contravariance: T <: U ->M[T] >: M[U] - Par exemple un Cat <: Animal, donc Function1[Cat, Unit] >: Function1[Animal, Unit]

ou invariant s'il n'y a pas de relation entre les deux.

Remarquez comment covariance préserve l'ordre entre les types, car une Cat dérive Animal. Maintenant, remarquez comment contravariance inverse la commande, puisque maintenant Function0[Animal, Unit] dérive un Function0[Cat, Unit]. Comment pouvons-nous prendre cette notion de variance à notre avantage? Sur la base de ces règles, nous pouvons généraliser la compatibilité des affectations entre constructeurs de types! De bons exemples sont List[A], Option[A] et Function1[-T, +U] (ou tout FunctionN vraiment). Prenons par exemple un Function1[-T, +U] (T => U) qui possède à la fois un paramètre covariant et un paramètre contravariant.

Pourquoi le paramètre de type d'entrée est-il contravariant et le type de sortie est-il covariant? Tout d'abord, selon les axiomes définis ci-dessus, nous pouvons voir que:

Function1[Sport,Int] <: Function1[Tennis,Int] 

Le paramètre de type d'entrée inverse la relation sur les types, car, généralement, Tennis <: Sport, mais ici il est opposé. Pourquoi cela est-il ainsi? Parce que toute fonction qui prend un Sport saura faire face à un Tennis, mais le contraire n'est pas vrai. Par exemple:

val sportFunc: (Sport => Int) = ??? 
val tennisFunc: (Tennis => Int) = sportFunc 

val result = tennisFunc(new Tennis()) 

Mais serait une fonction attendant un Tennis savoir comment faire face à toute Sport?Bien sûr que non:

val tennisFunc: (Tennis => Int) = ??? 
val sportFunc: (Sport => Int) = tennisFunc 

// The underlying function needs to deal with a Tennis, not a `FootBall`. 
val result = sportFunc(new FootBall()) 

Le contraire est vrai en ce qui concerne les types de sortie qui sont covariant, Toute personne attend un Sport comme type de retour peut faire face à un Tennis ou FootBall ou VollyBall.