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
}
oxbow_lakes
la source
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.

Daniel Spiewak
la source
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 :-)

Seth Tisue
la source
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.

Jorge Ortiz
la source
Vous avez raison; en essayant de simplifier mon exemple réel, j'ai fait une erreur triviale :-(
oxbow_lakes