2013-03-08 3 views
6

Ich implementiere eine Datenstruktur. Obwohl es in keiner der Standard-Sammeleigenschaften von Scala passt, möchte ich die to[Col[_]]-Methode einbeziehen, die in einer Builder-Fabrik Standard-Scala-Sammlungen generieren kann.Hinzufügen einer `to [Col [_]]` Methode für eine kovariante Sammlung

dieses Nehmen wir nun an, kopiert von GenTraversableOnce:

trait Foo[+A] { 
    def to[Col[_]](implicit cbf: CanBuildFrom[Nothing, A, Col[A]]): Col[A] 
} 

Dieser schlägt mit error: covariant type A occurs in invariant position.

Also wie kann GenTraversableOnce dies erreichen? Ich kann im Quellcode sehen, dass sie eine annotation.unchecked.uncheckedVariance ...

hinzufügen, die wie ein schmutziger Trick aussieht. Wenn der Tipper dies normalerweise ablehnt, wie kann das mit sicher und ausgeschaltet werden?

Antwort

2

Die Varianzprüfung ist ein sehr wichtiger Teil der Typprüfung und das Überspringen kann leicht zu einem Laufzeittypfehler führen. Hier kann ich einen Typ demonstrieren, der mit einem ungültigen Laufzeitwert gefüllt ist, indem ich ihn drucke. Ich war jedoch nicht in der Lage, es mit einer Typ-Cast-Ausnahme zum Absturz zu bringen.

import collection.generic.CanBuildFrom 
import collection.mutable.Builder 
import scala.annotation.unchecked.uncheckedVariance 

trait Foo[+A] { 
    def toCol[Col[_]](implicit cbf: CanBuildFrom[Nothing, A, Col[A @uncheckedVariance]]): Col[A @uncheckedVariance] 
} 

object NoStrings extends Foo[String] { 
    override def toCol[Col[_]](implicit cbf: CanBuildFrom[Nothing, String, Col[String]]): Col[String] = { 
    val res : Col[String] = cbf().result 
    println("Printing a Col[String]: ") 
    println(res) 
    res 
    } 
} 

case class ExactlyOne[T](t : T) 

implicit def buildExactlyOne = new CanBuildFrom[Nothing, Any, ExactlyOne[Any]] { 
    def apply() = new Builder[Any, ExactlyOne[Any]] { 
    def result = ExactlyOne({}) 
    def clear = {} 
    def +=(x : Any) = this 
    } 
    def apply(n : Nothing) = n 
} 

val noStrings : Foo[Any] = NoStrings 
noStrings.toCol[ExactlyOne] 

Hier println(res) mit res : Col[String] druckt ExactlyOne(()). ExactlyOne(()) hat jedoch keinen Typ Col[String], der einen Typfehler anzeigt.

Um das Problem zu lösen, während die Varianz Regeln respektieren können wir den unveränderlichen Code aus dem Zug bewegen und nur covariant Teil halten, während die implizite Konvertierung unter Verwendung von covariant Charakterzug invariant Hilfsklasse zu konvertieren:

import collection.generic.CanBuildFrom 

trait Foo[+A] { 
    def to[R](implicit cbf: CanBuildFrom[Nothing, A, R]): R 
} 

class EnrichedFoo[A](foo : Foo[A]) { 
    def toCol[Col[_]](implicit cbf: CanBuildFrom[Nothing, A, Col[A]]): Col[A] = 
    foo.to[Col[A]] 
} 

implicit def enrich[A](foo : Foo[A]) = new EnrichedFoo(foo) 

case class Bar[A](elem: A) extends Foo[A] { 
    def to[R](implicit cbf: CanBuildFrom[Nothing, A, R]): R = { 
    val b = cbf() 
    b += elem 
    b.result() 
    } 
} 

val bar1 = Bar(3) 
println(bar1.toCol[Vector]) 
2

Es kann, weil es die @uncheckedVariance Annotation verwendet, um das Typsystem zu umgehen und Varianzprüfung zu ignorieren.

einfach import scala.annotation.unchecked.uncheckedVariance und mit Anmerkungen versehen, die für die Sie die Varianz deaktiviert Überprüfung wollen:

def to[Col[_]](implicit cbf: CanBuildFrom[Nothing, A, Col[A @uncheckedVariance]]): Col[A @uncheckedVariance] 

Hier finden Sie eine vollständigere Erklärung in the related answer.

2

Ich lese den Link zu der anderen Frage von @ axel22 erwähnt. Es scheint jedoch immer noch nicht der eigentliche Grund dafür zu sein (GenTraversableOnce zu erlauben, sowohl für variante als auch für invariante Sammlungen zu funktionieren-es ist kovariant in A).

Zum Beispiel arbeitet das folgende richtig, ohne die typer Nötigung:

import collection.generic.CanBuildFrom 

trait Foo[+A] { 
    def to[A1 >: A, Col[_]](implicit cbf: CanBuildFrom[Nothing, A1, Col[A1]]): Col[A1] 
} 

case class Bar[A](elem: A) extends Foo[A] { 
    def to[A1 >: A, Col[_]](implicit cbf: CanBuildFrom[Nothing, A1, Col[A1]]): Col[A1]= { 
    val b = cbf() 
    b += elem 
    b.result() 
    } 
} 

Dies würde meiner Meinung nach die richtige Signatur. Aber dann natürlich, wird es hässlich:

val b = Bar(33) 
b.to[Int, Vector] 

Also, meine Interpretation der Verwendung von @uncheckedVariance nur ist zu vermeiden, den Elementtyp zu wiederholen (als obere Schranke) in der to Signatur.

Das beantwortet aber immer noch nicht, wenn wir uns einen Fall vorstellen können, der dazu führt, dass ein Laufzeitfehler die Varianz vernachlässigt?

+1

Sie don brauche wirklich 'A' und 'A1' überhaupt nicht. Das funktioniert auch: 'def zu [B, Col [_]] (implizite cbf: CanBuildFrom [Nichts, A, Col [B]]): Col [B]'. Auch das sieht unnötig einschränkend aus, da es nur das Ergebnis der Form "Col [B]" erlaubt. Dies sieht allgemeiner und einfacher aus: 'def zu [R] (implizite cbf: CanBuildFrom [Nichts, A, R]): R'. – Rotsor

Verwandte Themen