Perché l'insieme immutabile di Scala non è covariante nel suo tipo?


94

EDIT : riscritto questa domanda sulla base della risposta originale

La scala.collection.immutable.Setclasse non è covariante nel suo parametro di tipo. Perchè è questo?

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
}

Vale la pena notare che si foo(s.toSet[CharSequence])compila bene. Il toSetmetodo è O (1): avvolge asInstanceOf.
john sullivan

1
Nota anche che foo(Set("Hello", "World"))compila anche su 2.10, poiché Scala sembra essere in grado di dedurre il giusto tipo di Set. Tuttavia, non funziona con le conversioni implicite ( stackoverflow.com/questions/23274033/… ).
LP_

Risposte:


55

Setè invariante nel suo parametro di tipo a causa del concetto alla base degli insiemi come funzioni. Le seguenti firme dovrebbero chiarire leggermente le cose:

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

Se Setfosse covariante in A, il applymetodo non sarebbe in grado di accettare un parametro di tipo a Acausa della controvarianza delle funzioni. Setpotrebbe potenzialmente essere controvariante in A, ma anche questo causa problemi quando vuoi fare cose come questa:

def elements: Iterable[A]

In breve, la soluzione migliore è mantenere le cose invarianti, anche per la struttura dei dati immutabile. Noterai che immutable.Mapè invariante anche in uno dei suoi parametri di tipo.


4
Immagino che questo argomento sia imperniato sul "concetto alla base degli insiemi come funzioni" - potrebbe essere ampliato? Ad esempio, quali vantaggi mi offre "un insieme come funzione" rispetto a un "insieme come raccolta" no? Vale la pena perdere l'uso di quel tipo covariante?
oxbow_lakes

23
La firma del tipo è un esempio piuttosto debole. "Applica" di un insieme è la stessa cosa che contiene il metodo. Purtroppo, Scala's List è co-variante e ha anche un metodo contiene. La firma per List's contiene è diversa, ovviamente, ma il metodo funziona proprio come quello di Set. Quindi non c'è nulla che impedisca a Set di essere una co-variante, tranne una decisione di progettazione.
Daniel C. Sobral

6
Gli insiemi non sono funzioni booleane da una prospettiva matematica. Gli insiemi sono "costruiti" dagli assiomi di Zermelo-Fraenkel non ridotti da qualche funzione di inclusione. La ragione di ciò è il paradosso di Russell: se qualcosa può essere un membro di un insieme, allora considera l'insieme R degli insiemi che non sono membri di se stessi. Quindi fai la domanda: R è un membro di R?
oxbow_lakes

12
Non sono ancora convinto che valesse la pena sacrificare la covarianza per Set. Certo, è bello che sia un predicato, ma di solito puoi essere un po 'più prolisso e usare "set.contains" invece di "set" (e probabilmente "set.contains" si legge meglio in molti casi comunque).
Matt R

4
@ Martin: Poiché il metodo contiene di List accetta Any, non A. Il tipo di List(1,2,3).contains _è (Any) => Boolean, mentre il tipo di Set(1,2,3).contains _è res1: (Int) => Boolean.
Seth Tisue,

52

su http://www.scala-lang.org/node/9764 Martin Odersky scrive:

"Sulla questione degli insiemi, credo che la non varianza derivi anche dalle implementazioni. Gli insiemi comuni sono implementati come tabelle hash, che sono array non varianti del tipo di chiave. Sono d'accordo che sia un'irregolarità leggermente fastidiosa".

Quindi, sembra che tutti i nostri sforzi per costruire una ragione di principio per questo siano stati fuorvianti :-)


1
Ma alcune sequenze sono implementate anche con gli array, ed Seqè ancora covariante ... mi manca qualcosa?
LP_

4
Questo potrebbe essere risolto banalmente archiviando Array[Any]internamente.
destra il

@rightfold è corretto. Potrebbe esserci un motivo ragionevole, ma non è questo.
Paul Draper

6

EDIT : per chiunque si chieda perché questa risposta sembra leggermente fuori tema, questo perché io (l'interrogante) ho modificato la domanda.

L'inferenza del tipo di Scala è abbastanza buona per capire che vuoi CharSequences e non Strings in alcune situazioni. In particolare, il seguente funziona per me in 2.7.3:

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

Per quanto riguarda come creare direttamente immutable.HashSets: non farlo. Come ottimizzazione dell'implementazione, immutable.HashSet di meno di 5 elementi non sono in realtà istanze di immutable.HashSet. Sono EmptySet, Set1, Set2, Set3 o Set4. Queste classi sottoclasse immutable.Set, ma non immutable.HashSet.


Hai ragione; nel tentativo di semplificare il mio esempio attuale ho commesso un errore banale :-(
oxbow_lakes
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.