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
}

É 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.


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 :-)


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.


Você está certo; ao tentar simplificar meu exemplo real, cometi um erro trivial :-(
oxbow_lakes
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.