2012-08-04 9 views
5

Warum benötige ich in dieser parametrisierten Funktion den Cast? Und wie kann ich es loswerden?Warum benötige ich in dieser parametrierten Scala-Funktion den Cast?

/** Filters `xs` to have only every nth element. 
    */ 
def everyNth[A <% Iterable[B], B](xs: A, n: Int, offset: Int = 0): A = 
    (xs.zipWithIndex collect { case (x, i) if (i - offset) % n == 0 => x }).asInstanceOf[A] 

Wenn ich nicht die Besetzung am Ende haben, bekomme ich diese Fehlermeldung:

type mismatch; found : Iterable[B] required: A 

Diese Funktion (mit der Besetzung) eignen sich für alle Fälle habe ich versucht, es auf und ich weiß von Dingen wie die folgende an den REPL eingeben, dass Scala der Lage ist, den Ergebnistyp richtig, wenn sie nicht im Zusammenhang mit einer parametrierbaren Funktion ableiten:

scala> val a: Stream[Int] = (Stream.from(0).zipWithIndex collect { case (x, i) if (i + 3) % 5 == 0 => x }) 
a: Stream[Int] = Stream(2, ?) 

scala> a take 10 force 
res20: scala.collection.immutable.Stream[Int] = Stream(2, 7, 12, 17, 22, 27, 32, 37, 42, 47) 

Bitte erläutern!

+0

Ähnliche Frage, die 'CanBuildFrom' verwendet, um das Problem zu umgehen: [Funktion, die generisch einen Typ übernimmt und den gleichen Typ zurückgibt] (http://stackoverflow.com/questions/10019529/function-which-generically-takes- a-Typ-und-gibt den gleichen Typ zurück. Ich kann es nicht schaffen, mit dieser Frage zu arbeiten, jemand anderes? – sschaef

+0

Ich bekam CanBuildFrom, um für meine Frage zu arbeiten, und legte die Lösung in eine Antwort. Sehen Sie die Antwort unten, wenn Sie neugierig sind. – Douglas

+0

Schöne Antwort! Übrigens kannst du deine eigenen Antworten akzeptieren ... – sschaef

Antwort

4

Gemäß einigen einige Vorschläge in den Kommentaren, schaute ich in CanBuildFrom, und das ist, was ich kam mit:

import scala.collection.IterableLike 
import scala.collection.generic.CanBuildFrom 

/** Filters `xs` to have only every nth element. 
    */ 
def everyNth[A, It <: Iterable[A]] 
     (xs: It with IterableLike[A, It], n: Int, offset: Int = 0) 
     (implicit bf: CanBuildFrom[It, A , It]): It = { 
    val retval = bf() 
    retval ++= xs.zipWithIndex collect { case (x, i) if (i - offset) % n == 0 => x } 
    retval.result  
} 

Yay, es funktioniert !!!

Und es gibt NO Besetzung. Als solches funktioniert es sogar für Bereiche.

Allerdings muss man mit einem leeren Retval beginnen und dann "++ =" verwenden, um es aufzufüllen. Es erscheint ein wenig unelegant, also wenn jemand eine elegantere Lösung hat, bin ich ganz Ohr.

Hier ist eine weitere generische Funktion, die ich implementiert habe, die ein bisschen schwieriger als die oben genannten war, da der Rückgabetyp nicht der gleiche wie der Argumenttyp ist. I.e., Die Eingabe eine Folge von A ist ‚s, aber der Ausgang ist eine Folge von (A, A)‘ s:

def zipWithSelf[A, It[A] <: Iterable[A]] 
     (xs: It[A] with IterableLike[A, It[A]]) 
     (implicit bf: CanBuildFrom[It[A], (A, A), It[(A, A)]]): It[(A, A)] = { 
    val retval = bf() 
    if (xs.nonEmpty) { 
     retval ++= xs zip xs.tail 
     retval.result 
    } else retval.result 
} 

Und hier ist ein andere:

/** Calls `f(x)` for all x in `xs` and returns an Iterable containing the indexes for 
    * which `f(x)` is true. 
    * 
    * The type of the returned Iterable will match the type of `xs`. 
    */ 
def findAll[A, It[A] <: Iterable[A]] 
     (xs: It[A] with IterableLike[A, It[A]]) 
     (f: A => Boolean) 
     (implicit bf: CanBuildFrom[It[A], Int, It[Int]]): It[Int] = { 
    val retval = bf() 
    retval ++= xs.zipWithIndex filter { p => f(p._1) } map { _._2 } 
    retval.result 
} 

Ich habe noch kein tiefes Verständnis für die "Like" -Typen und CanBuildFrom, aber ich bekomme das Wesentliche. Und es ist in den meisten Fällen einfach genug, die Casting-Version einer generischen Funktion als ersten Durchlauf zu schreiben und dann die CanBuildFrom und IterableLike Boilerplate hinzuzufügen, um die Funktion allgemeiner und vollständig typsicher zu machen.

3

Es gibt einige Fälle, in denen collect nicht denselben Subtyp von Iterable zurückzukehren, wie es auf, im Falle eines Range zum Beispiel genannt wurde:

scala> everyNth(1 to 10, 2) 
java.lang.ClassCastException: scala.collection.immutable.Vector cannot be cast to scala.collection.immutable.Range$Inclusive 
     at .<init>(<console>:9) 
     at .<clinit>(<console>) 
     at .<init>(<console>:11) 
     at .<clinit>(<console>) 
     at $print(<console>) 
     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) 
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
     at java.lang.reflect.Method.invoke(Method.java:616) 
     at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:704) 
     at scala.tools.nsc.interpreter.IMain$Request$$anonfun$14.apply(IMain.scala:920) 
     at scala.tools.nsc.interpreter.Line$$anonfun$1.apply$mcV$sp(Line.scala:43) 
     at scala.tools.nsc.io.package$$anon$2.run(package.scala:25) 
     at java.lang.Thread.run(Thread.java:679) 
+0

Ah, natürlich. Stupid Ranges! Gibt es eine andere Eigenschaft, die solche schlecht benommenen Sequenzen ausschließt? Oder sollte ich einfach mit der Besetzung leben? – Douglas

+0

Ich denke, der richtige Weg, um es zu tun wäre die Verwendung der CanBuildFrom Magie in der Sammlung API verwendet? –

+0

Ich habe CanBuildFrom, um für meine Frage zu arbeiten, und stelle die Lösung in eine Antwort neben dieser. – Douglas

1

Das Problem hierbei ist, dass durch collect Aufruf auf xs Sie konvertieren es in Iterable[B]. A <% Iterable[B] bedeutet, dass A als Iterable[B] angesehen werden kann, was nicht unbedingt bedeutet, dass Iterable[B] auch als A angesehen werden kann. Was passiert hier eigentlich ist

def everyNth[A, B](xs: A, n: Int, offset: Int = 0)(implicit view: (A => Iterable[B])): A = 
    (view(xs).zipWithIndex collect { 
    case (x, i) if (i + offset) % n == 0 => x 
    }).asInstanceOf[A] 

Wenn ich zum Beispiel haben diese:

class Foo 
implicit def foo2Iterable(foo: Foo) = List(foo) 

und rufen

everyNth(new Foo, 2) 

I

bekommen
java.lang.ClassCastException: scala.collection.immutable.$colon$colon cannot be cast to Foo 

Sie hier Gießen vermeiden sollten. Entweder fügen Sie eine Ansicht von Iterable[B] => A

edit: Typ gebunden funktioniert hier nicht.

+0

Das Ersetzen der Ansichtsbegrenzung durch eine Typbegrenzung beseitigt hier nicht die Notwendigkeit einer Umwandlung, weshalb ich nicht genau weiß, was Sie behaupten. – Douglas

+0

Entschuldigung, Sie haben Recht. Das Hinzufügen einer Ansicht von Iterable [B] => A wäre dann die einzige Lösung. – drexin

Verwandte Themen