Domanda

In seguito a questa domanda , qualcuno può spiegare quanto segue in Scala:

class Slot[+T] (var some: T) { 
   //  DOES NOT COMPILE 
   //  "COVARIANT parameter in CONTRAVARIANT position"

}

Comprendo la distinzione tra +T e T nella dichiarazione del tipo (viene compilata se utilizzo Slot ) . Ma allora come si può effettivamente scrivere una classe che è covariante nel suo parametro type senza ricorrere alla creazione della cosa non parametrizzata ? Come posso assicurarmi che quanto segue possa essere creato solo con un'istanza di var ?

class Slot[+T] (var some: Object){    
  def get() = { some.asInstanceOf[T] }
}

MODIFICA : ora riducilo a quanto segue:

abstract class _Slot[+T, V <: T] (var some: V) {
    def getT() = { some }
}

va bene, ma ora ho due parametri di tipo, dove ne voglio solo uno. Riproverò la domanda in questo modo:

Come posso scrivere una immutabile val classe che è covariante nel suo tipo?

MODIFICA 2 : Duh! Ho usato <=> e non <=>. Quello che volevo è ciò che volevo:

class Slot[+T] (val some: T) { 
}
È stato utile?

Soluzione

Generalmente, un parametro di tipo covariante è uno che può variare in base alla sottotipo della classe (in alternativa, variare con il sottotipo, quindi il " co - " prefisso). Più concretamente:

trait List[+A]

List[Int] è un sottotipo di List[AnyVal] perché Int è un sottotipo di AnyVal. Ciò significa che è possibile fornire un'istanza di String quando è previsto un valore di tipo Integer[]. Questo è davvero un modo molto intuitivo per far funzionare i generici, ma si scopre che è insensato (rompe il sistema dei tipi) se usato in presenza di dati mutabili. Questo è il motivo per cui i generici sono invarianti in Java. Breve esempio di instabilità nell'uso di array Java (erroneamente covarianti):

Object[] arr = new Integer[1];
arr[0] = "Hello, there!";

Abbiamo appena assegnato un valore di tipo ArrayStoreException a una matrice di tipo Array. Per ragioni che dovrebbero essere ovvie, questa è una cattiva notizia. Il sistema di tipi Java in realtà lo consente al momento della compilazione. La JVM & Quot; utile & Quot; lancia un [A] in fase di esecuzione. Il sistema di tipi di Scala evita questo problema perché il parametro type sulla classe [+A] è invariante (la dichiarazione è P anziché Function1).

Nota che esiste un altro tipo di varianza noto come contraddizione . Questo è molto importante perché spiega perché la covarianza può causare alcuni problemi. La contraddizione è letteralmente l'opposto della covarianza: i parametri variano verso l'alto con il sottotipo. È molto meno comune in parte perché è così controintuitivo, sebbene abbia un'applicazione molto importante: le funzioni.

trait Function1[-P, +R] {
  def apply(p: P): R
}

Nota il " - " annotazione varianza sul parametro R tipo. Questa dichiarazione nel suo insieme indica che T1' è contraddittorio in T1 e covariante in T2. Pertanto, possiamo derivare i seguenti assiomi:

T1' <: T1
T2 <: T2'
---------------------------------------- S-Fun
Function1[T1, T2] <: Function1[T1', T2']

Nota che T2' deve essere un sottotipo (o lo stesso tipo) di A, mentre è l'opposto di cons e List. In inglese, questo può essere letto come segue:

  

Una funzione A è un sottotipo di un'altra funzione B se il tipo di parametro di A è un supertipo del tipo di parametro di B mentre il tipo restituito di A è un sottotipo del tipo restituito di B .

Il motivo di questa regola è lasciato al lettore come esercizio (suggerimento: pensa a casi diversi poiché le funzioni sono sottotitolate, come il mio esempio di matrice dall'alto).

Con le tue nuove conoscenze di co-e contravarianza, dovresti essere in grado di capire perché il seguente esempio non verrà compilato:

trait List[+A] {
  def cons(hd: A): List[A]
}

Il problema è che List[A] è covariante, mentre la funzione B prevede che il suo parametro di tipo sia invariante . Pertanto, <=> sta variando la direzione sbagliata. È interessante notare che potremmo risolvere questo problema rendendo <=> contraddittorio in <=>, ma il tipo restituito <=> non sarebbe valido poiché la funzione <=> prevede che il suo tipo restituito sia covariante .

Le nostre uniche due opzioni qui sono: a) rendere <=> invariante, perdendo le proprietà intuitive e intuitive della covarianza, oppure b) aggiungere un parametro di tipo locale al metodo <=> che definisce <=> come un limite inferiore:

def cons[B >: A](v: B): List[B]

Questo è ora valido. Puoi immaginare che <=> stia cambiando verso il basso, ma <=> è in grado di variare verso l'alto rispetto a <=> poiché <=> è il limite inferiore. Con questa dichiarazione del metodo, possiamo avere <=> essere covarianti e tutto funziona.

Nota che questo trucco funziona solo se restituiamo un'istanza di <=> specializzata nel tipo meno specifico <=>. Se si tenta di rendere <=> mutevole, le cose si rompono da quando si finisce per provare ad assegnare valori di tipo <=> a una variabile di tipo <=>, che non è consentita dal compilatore. Ogni volta che hai una mutabilità, devi avere un mutatore di qualche tipo, che richiede un parametro del metodo di un certo tipo, che (insieme all'accessorio) implica invarianza. Covariance funziona con dati immutabili poiché l'unica operazione possibile è un accessor, a cui può essere assegnato un tipo di ritorno covariante.

Altri suggerimenti

@Daniel l'ha spiegato molto bene. Ma per spiegarlo in breve, se fosse permesso:

  class Slot[+T](var some: T) {
    def get: T = some   
  }

  val slot: Slot[Dog] = new Slot[Dog](new Dog)   
  val slot2: Slot[Animal] = slot  //because of co-variance 
  slot2.some = new Animal   //legal as some is a var
  slot.get ??

slot.get genererà quindi un errore in fase di esecuzione poiché non è riuscito a convertire un Animal in Dog (duh!).

In generale la mutabilità non va bene con la co-varianza e la contro-varianza. Questo è il motivo per cui tutte le raccolte Java sono invarianti.

Vedi Scala per esempio , pagina 57+ per una discussione completa di questo.

Se capisco correttamente il tuo commento, devi rileggere il passaggio a partire dalla fine di pagina 56 (in pratica, ciò che penso che stai chiedendo non è sicuro per i tipi senza controlli del tempo di esecuzione, che scala non sì, quindi sei sfortunato). Traducendo il loro esempio per usare il tuo costrutto:

val x = new Slot[String]("test") // Make a slot
val y: Slot[Any] = x             // Ok, 'cause String is a subtype of Any
y.set(new Rational(1, 2))        // Works, but now x.get() will blow up 

Se ritieni di non capire la tua domanda (una possibilità distinta), prova ad aggiungere ulteriori spiegazioni / contesto alla descrizione del problema e riproverò.

In risposta alla tua modifica: le slot immutabili sono una situazione completamente diversa ... * sorridi * Spero che l'esempio sopra sia stato utile.

È necessario applicare un limite inferiore al parametro. Sto facendo fatica a ricordare la sintassi, ma penso che sarebbe simile a questo:

class Slot[+T, V <: T](var some: V) {
  //blah
}

La Scala per esempio è un po 'difficile da capire, alcuni esempi concreti avrebbero aiutato.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top