web-dev-qa-db-fra.com

scala - Tout trait de soulignement vs vs dans les génériques

Quelle est la différence entre les définitions génériques suivantes dans Scala:

class Foo[T <: List[_]]

et

class Bar[T <: List[Any]]

Mon instinct me dit qu'ils sont à peu près les mêmes mais que ce dernier est plus explicite. Je trouve des cas où le premier compile mais pas le second, mais ne peux pas mettre le doigt sur la différence exacte.

Merci!

Modifier:

Puis-je en jeter un autre dans le mix?

class Baz[T <: List[_ <: Any]]
65
Sean Connolly

OK, j'ai pensé que je devrais avoir mon point de vue, au lieu de simplement poster des commentaires. Désolé, cela va être long, si vous voulez que le TL; DR saute à la fin.

Comme l'a dit Randall Schulz, ici _ Est un raccourci pour un type existentiel. À savoir,

class Foo[T <: List[_]]

est un raccourci pour

class Foo[T <: List[Z] forSome { type Z }]

Notez que contrairement à ce que mentionne la réponse de Randall Shulz (divulgation complète: je me suis trompé aussi dans une version antérieure de ce post, merci à Jesper Nordenberg de l'avoir signalé), ce n'est pas la même chose que:

class Foo[T <: List[Z]] forSome { type Z }

ce n'est pas la même chose que:

class Foo[T <: List[Z forSome { type Z }]]

Attention, il est facile de se tromper (comme le montre mon goof précédent): l'auteur de l'article référencé par la réponse de Randall Shulz s'est trompé lui-même (voir commentaires), et l'a corrigé plus tard. Mon principal problème avec cet article est que dans l'exemple illustré, l'utilisation d'existentielles est censée nous sauver d'un problème de frappe, mais ce n'est pas le cas. Allez vérifier le code et essayez de compiler compileAndRun(helloWorldVM("Test")) ou compileAndRun(intVM(42)). Oui, ne compile pas. Faire simplement compileAndRun générique dans A rendrait le code compilable, et ce serait beaucoup plus simple. En bref, ce n'est probablement pas le meilleur article pour en savoir plus sur les existentiels et leur utilité (l'auteur lui-même reconnaît dans un commentaire que l'article "doit être rangé").

Je recommande donc plutôt la lecture de cet article: http://www.artima.com/scalazine/articles/scalas_type_system.html , en particulier les sections nommées "Types existentiels" et "Variance in Java et Scala ".

Le point important que vous devriez tirer de cet article est que les existentielles sont utiles (en plus de pouvoir gérer les classes génériques Java classes) lorsque vous traitez avec des types non covariants. Voici un exemple.

case class Greets[T]( private val name: T ) {
  def hello() { println("Hello " + name) }
  def getName: T = name
}

Cette classe est générique (notez également qu'elle est invariante), mais nous pouvons voir que hello n'utilise vraiment pas le paramètre type (contrairement à getName), donc si j'obtiens un instance de Greets Je devrais toujours pouvoir l'appeler, quel que soit T. Si je veux définir une méthode qui prend une instance Greets et appelle simplement sa méthode hello, je pourrais essayer ceci:

def sayHi1( g: Greets[T] ) { g.hello() } // Does not compile

Effectivement, cela ne compile pas, car T sort de nulle part ici.

OK alors, rendons la méthode générique:

def sayHi2[T]( g: Greets[T] ) { g.hello() }
sayHi2( Greets("John"))
sayHi2( Greets('Jack))

Génial, cela fonctionne. Nous pourrions également utiliser des existentiels ici:

def sayHi3( g: Greets[_] ) { g.hello() }
sayHi3( Greets("John"))
sayHi3( Greets('Jack))

Fonctionne aussi. Donc, dans l'ensemble, il n'y a aucun avantage réel à utiliser un paramètre existentiel (comme dans sayHi3) Par rapport au type (comme dans sayHi2).

Cependant, cela change si Greets apparaît lui-même comme paramètre de type dans une autre classe générique. Dites par exemple que nous voulons stocker plusieurs instances de Greets (avec différents T) dans une liste. Essayons:

val greets1: Greets[String] = Greets("John")
val greets2: Greets[Symbol] = Greets('Jack)
val greetsList1: List[Greets[Any]] = List( greets1, greets2 ) // Does not compile

La dernière ligne ne se compile pas car Greets est invariante, donc un Greets[String] Et Greets[Symbol] Ne peuvent pas être traités comme un Greets[Any] Même si String et Symbol les deux extensions Any.

OK, essayons avec une existentielle, en utilisant la notation abrégée _:

val greetsList2: List[Greets[_]] = List( greets1, greets2 ) // Compiles fine, yeah

Cela compile très bien, et vous pouvez faire, comme prévu:

greetsSet foreach (_.hello)

Maintenant, rappelez-vous que la raison pour laquelle nous avons eu un problème de vérification de type en premier lieu était que Greets est invariant. Si elle avait été transformée en classe covariante (class Greets[+T]), Alors tout aurait fonctionné hors de la boîte et nous n'aurions jamais eu besoin d'existentiels.


Donc, pour résumer, les existentielles sont utiles pour traiter les classes invariantes génériques, mais si la classe générique n'a pas besoin de s'afficher en tant que paramètre de type dans une autre classe générique, il est probable que vous n'ayez pas besoin d'existentielles et d'ajouter simplement un paramètre de type à votre méthode fonctionnera

Revenons maintenant (enfin, je sais!) À votre question spécifique, concernant

class Foo[T <: List[_]]

Parce que List est covariant, ceci est à toutes fins utiles identique à dire simplement:

class Foo[T <: List[Any]]

Donc, dans ce cas, l'utilisation de l'une ou l'autre notation n'est vraiment qu'une question de style.

Cependant, si vous remplacez List par Set, les choses changent:

class Foo[T <: Set[_]]

Set est invariant et nous sommes donc dans la même situation qu'avec la classe Greets de mon exemple. Ainsi, ce qui précède est vraiment très différent de

class Foo[T <: Set[Any]]
88

Le premier est un raccourci pour un type existentiel lorsque le code n'a pas besoin de savoir quel est le type ou de le contraindre:

class Foo[T <: List[Z forSome { type Z }]]

Ce formulaire indique que le type d'élément de List est inconnu de class Foo plutôt que votre deuxième formulaire, qui indique spécifiquement que le type d'élément de List est Any.

Consultez cette brève explication article de blog sur les types existentiels dans Scala ( [~ # ~] éditez [~ # ~] : ce lien est maintenant mort, un instantané est disponible sur archive.org )

6
Randall Schulz