Por que o conjunto imutável de Scala não é covariante em seu tipo?

94

EDIT : Reescrita esta pergunta com base na resposta original

A scala.collection.immutable.Setclasse não é covariante em seu parâmetro de tipo. Por que é isso?

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
fonte
É importante notar que foo(s.toSet[CharSequence])compila bem. O toSetmétodo é O (1) - ele apenas envolve asInstanceOf.
john sullivan
1
Observe também que foo(Set("Hello", "World"))compila também no 2.10, já que Scala parece ser capaz de inferir o tipo certo de Conjunto. Porém, ele não funciona com conversões implícitas ( stackoverflow.com/questions/23274033/… ).
LP_

Respostas:

55

Seté invariável em seu parâmetro de tipo devido ao conceito por trás dos conjuntos como funções. As seguintes assinaturas devem esclarecer um pouco as coisas:

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

Se Setfosse covariante em A, o applymétodo seria incapaz de tomar um parâmetro do tipo Adevido à contravariância de funções. Setpoderia ser potencialmente contravariant em A, mas isso também causa problemas quando você quer fazer coisas como esta:

def elements: Iterable[A]

Resumindo, a melhor solução é manter as coisas invariáveis, mesmo para a estrutura de dados imutável. Você notará que immutable.Maptambém é invariável em um de seus parâmetros de tipo.

Daniel Spiewak
fonte
4
Eu acho que esse argumento gira em torno do "conceito por trás dos conjuntos como funções" - isso poderia ser expandido? Por exemplo, quais vantagens "um conjunto como uma função" me dá que um "conjunto como uma coleção" não? Vale a pena perder o uso desse tipo covariante?
oxbow_lakes
23
A assinatura de tipo é um exemplo bastante fraco. O "aplicar" de um conjunto é a mesma coisa que o método contém. Infelizmente, a lista de Scala é covariada e também possui um método contém. A assinatura para o contém de List é diferente, é claro, mas o método funciona como o de Set. Portanto, não há nada que realmente impeça Set de ser co-variante, exceto uma decisão de design.
Daniel C. Sobral
6
Os conjuntos não são funções booleanas de uma perspectiva matemática. Os conjuntos são "construídos" a partir dos axiomas de Zermelo-Fraenkel não reduzidos por alguma função de inclusão. A razão por trás disso é o paradoxo de Russell: se algo pode ser membro de um conjunto, considere o Conjunto R de conjuntos que não são membros de si mesmos. Em seguida, faça a pergunta: R é membro de R?
oxbow_lakes
12
Ainda não estou convencido de que sacrificar a covariância valeu a pena para Set. Claro, é bom que seja um predicado, mas você geralmente pode ser um pouco mais prolixo e usar "set.contains" em vez de "set" (e possivelmente "set.contains" lê melhor em muitos casos).
Matt R
4
@Martin: Porque o método contém de List leva Any, não A. O tipo de List(1,2,3).contains _é (Any) => Boolean, enquanto o tipo de Set(1,2,3).contains _é res1: (Int) => Boolean.
Seth Tisue
52

em http://www.scala-lang.org/node/9764 Martin Odersky escreve:

"Sobre a questão dos conjuntos, acredito que a não variação deriva também das implementações. Conjuntos comuns são implementados como hashtables, que são arrays não variantes do tipo chave. Concordo que é uma irregularidade um pouco irritante."

Então, parece que todos os nossos esforços para construir uma razão de princípio para isso foram equivocados :-)

Seth Tisue
fonte
1
Mas algumas sequências também são implementadas com matrizes, e ainda Seqé covariante ... estou faltando alguma coisa?
LP_
4
Isso poderia ser resolvido trivialmente armazenando Array[Any]internamente.
direita,
@rightfold está correto. Pode haver um motivo razoável, mas não é este.
Paul Draper
6

EDITAR : para quem está se perguntando por que essa resposta parece um pouco fora do tópico, isso é porque eu (o questionador) modifiquei a pergunta.

A inferência de tipo do Scala é boa o suficiente para descobrir que você deseja CharSequences e não Strings em algumas situações. Em particular, o seguinte funciona para mim em 2.7.3:

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

Sobre como criar conjuntos.HashSets imutáveis ​​diretamente: não. Como uma otimização de implementação, immutable.HashSets com menos de 5 elementos não são realmente instâncias de immutable.HashSet. Eles são EmptySet, Set1, Set2, Set3 ou Set4. Essas classes são subclasses de immutable.Set, mas não de immutable.HashSet.

Jorge Ortiz
fonte
Você está certo; ao tentar simplificar meu exemplo real, cometi um erro trivial :-(
oxbow_lakes