Pourquoi l'ensemble immuable de Scala n'est-il pas covariant dans son type?


94

EDIT : réécrit cette question en fonction de la réponse originale

La scala.collection.immutable.Setclasse n'est pas covariante dans son paramètre de type. Pourquoi est-ce?

import scala.collection.immutable._

def foo(s: Set[CharSequence]): Unit = {
    println(s)
}

def bar(): Unit = {
   val s: Set[String] = Set("Hello", "World");
   foo(s); //DOES NOT COMPILE, regardless of whether type is declared 
           //explicitly in the val s declaration
}

Il est à noter que foo(s.toSet[CharSequence])compile bien. La toSetméthode est O (1) - elle s'enroule simplement asInstanceOf.
john sullivan

1
Notez aussi que foo(Set("Hello", "World"))compile aussi sur 2.10, puisque Scala semble être capable de déduire le bon type de Set. Cela ne fonctionne pas avec les conversions implicites ( stackoverflow.com/questions/23274033/… ).
LP_

Réponses:


55

Setest invariant dans son paramètre de type en raison du concept derrière les ensembles en tant que fonctions. Les signatures suivantes devraient clarifier légèrement les choses:

trait Set[A] extends (A=>Boolean) {
  def apply(e: A): Boolean
}

Si Setétaient covariantes dans A, la applyméthode serait incapable de prendre un paramètre de type en Araison de la contravariance des fonctions. Setpourraient être contravariant dans A, mais cela provoque aussi des problèmes lorsque vous voulez faire des choses comme ceci:

def elements: Iterable[A]

En bref, la meilleure solution est de garder les choses invariantes, même pour la structure de données immuable. Vous remarquerez que immutable.Mapc'est également invariant dans l'un de ses paramètres de type.


4
Je suppose que cet argument tourne autour du "concept derrière les ensembles en tant que fonctions" - pourrait-il être développé? Par exemple, quels avantages «un ensemble en tant que fonction» me donne-t-il qu'un «ensemble en tant que collection» ne me donne pas? Vaut-il la peine de perdre l'utilisation de ce type de covariant?
oxbow_lakes

23
La signature de type est un exemple assez faible. «Apply» d'un ensemble est la même chose que sa méthode contient. Hélas, la liste de Scala est co-variante et a également une méthode contient. La signature pour List's contains est différente, bien sûr, mais la méthode fonctionne exactement comme celle de Set. Rien n'empêche donc vraiment Set d'être co-variant, sauf une décision de conception.
Daniel C.Sobral

6
Les ensembles ne sont pas des fonctions booléennes d'un point de vue mathématique. Les ensembles sont "construits" à partir des axiomes de Zermelo-Fraenkel non réduits par une fonction d'inclusion. La raison derrière cela est le paradoxe de Russell: si quelque chose peut être membre d'un ensemble, alors considérez l'ensemble R des ensembles qui ne sont pas membres d'eux-mêmes. Puis posez la question: R est-il membre de R?
oxbow_lakes

12
Je ne suis toujours pas convaincu que sacrifier la covariance en valait la peine pour Set. Bien sûr, c'est bien que ce soit un prédicat, mais vous pouvez généralement être un peu plus verbeux et utiliser "set.contains" plutôt que "set" (et sans doute "set.contains" se lit mieux dans de nombreux cas de toute façon).
Matt R

4
@Martin: Parce que la méthode contient de List prend Any, pas A. Le type de List(1,2,3).contains _est (Any) => Boolean, tandis que le type de Set(1,2,3).contains _est res1: (Int) => Boolean.
Seth Tisue le

52

à http://www.scala-lang.org/node/9764 Martin Odersky écrit:

"En ce qui concerne les ensembles, je pense que la non-variance provient également des implémentations. Les ensembles communs sont implémentés sous forme de tables de hachage, qui sont des tableaux non-variant du type clé. Je conviens que c'est une irrégularité légèrement ennuyeuse."

Donc, il semble que tous nos efforts pour construire une raison de principe à cela ont été malavisés :-)


1
Mais certaines séquences sont également implémentées avec des tableaux, et sont toujours Seqcovariantes ... est-ce que je manque quelque chose?
LP_

4
Cela pourrait être résolu de manière triviale en stockant en Array[Any]interne.
droite

@rightfold est correct. Il y a peut-être une raison raisonnable, mais ce n'est pas ça.
Paul Draper

6

EDIT : pour quiconque se demande pourquoi cette réponse semble légèrement hors sujet, c'est parce que je (l'interlocuteur) ai modifié la question.

L'inférence de type de Scala est assez bonne pour comprendre que vous voulez des CharSequences et non des Strings dans certaines situations. En particulier, ce qui suit fonctionne pour moi dans 2.7.3:

import scala.collections.immutable._
def findCharSequences(): Set[CharSequence] = Set("Hello", "World")

Quant à savoir comment créer des immuables.HashSets directement: ne le faites pas. En tant qu'optimisation de l'implémentation, immutable.HashSets de moins de 5 éléments ne sont pas en fait des instances d'immutable.HashSet. Ils sont soit EmptySet, Set1, Set2, Set3 ou Set4. Ces classes sous-classe immutable.Set, mais pas immutable.HashSet.


Vous avez raison; en essayant de simplifier mon exemple réel, j'ai fait une erreur triviale :-(
oxbow_lakes
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.