2012-08-04 15 views
5

In questa funzione parametrizzata, perché ho bisogno del cast? E come posso liberarmene?In questa funzione Scala parametrizzata, perché ho bisogno del 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] 

Se non ho il cast, alla fine, ho questo messaggio di errore:

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

Questa funzione (con il cast) funziona per tutti i casi che ho provato su , e so di digitare cose come quanto segue al REPL che Scala è in grado di dedurre il tipo di risultato correttamente quando non nel contesto di una funzione con parametri:

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) 

si prega di spiegare!

+0

Domanda simile che utilizza 'CanBuildFrom' per aggirare il problema: [Funzione che prende genericamente un tipo e restituisce lo stesso tipo] (http://stackoverflow.com/questions/10019529/function-which-generically-takes- un-tipo-e-torna-the-same-type). Non riesco a farlo funzionare con questa domanda, qualcun altro? – sschaef

+0

Ho ottenuto CanBuildFrom per funzionare per la mia domanda e ho inserito la soluzione in una risposta. Guarda la risposta qui sotto se sei curioso. – Douglas

+0

Bella risposta! A proposito, puoi accettare le tue risposte ... – sschaef

risposta

4

Come da alcuni alcuni suggerimenti nei commenti, ho guardato in CanBuildFrom, e questo è ciò che mi si avvicinò con:

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, funziona !!!

E c'è NO cast. In quanto tale, funziona anche per gli intervalli.

Tuttavia, dover iniziare con un retval vuoto e quindi utilizzare "++ =" per riempirlo sembra un po 'poco elegante, quindi se qualcuno ha una soluzione più elegante, sono tutto orecchie.

Ecco un'altra funzione generica che ho implementato che era un po 'più complicata di quella sopra perché il tipo di ritorno non è lo stesso del tipo di argomento. Cioè, L'ingresso è una sequenza di A 's, ma l'uscita è una sequenza di (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 
} 

Ed ecco un altro:

/** 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 
} 

io ancora non hanno alcuna comprensione profonda della i tipi "Mi piace" e CanBuildFrom, ma ottengo l'essenza. Ed è abbastanza facile, nella maggior parte dei casi, scrivere la versione di casting di una funzione generica come prima passata, quindi aggiungere il codice CanBuildFrom e IterableLike per rendere la funzione più generale e completamente sicura.

3

Ci sono alcuni casi in cui collect non restituisce lo stesso sottotipo di Iterable come veniva chiamato in poi, per esempio nel caso di un Range:

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, certo. Stupidi Ranges! C'è qualche altro tratto che può essere usato che esclude sequenze così maleducate? O dovrei semplicemente vivere con il cast? – Douglas

+0

Immagino che il modo corretto per farlo sarebbe utilizzare la magia CanBuildFrom usata nell'API di raccolta? –

+0

Ho ottenuto CanBuildFrom per funzionare per la mia domanda e ho messo la soluzione in una risposta accanto a questa. – Douglas

1

Il problema qui è che chiamando collect su xs lo converti in Iterable[B]. A <% Iterable[B] significa che A può essere visualizzato come Iterable[B], il che non significa necessariamente che Iterable[B] può anche essere visualizzato come A. Quello che succede in realtà qui è

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] 

Quando ho ad esempio questo:

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

e chiamo

everyNth(new Foo, 2) 

ottengo

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

Si dovrebbe evitare di lanciare qui. O aggiungi una vista da Iterable[B] => A

modifica: il tipo di limite non funziona qui.

+0

Sostituire la vista rilegata con un tipo rilegato non elimina la necessità di un cast qui, quindi non sono esattamente chiaro su cosa stai affermando. – Douglas

+0

Siamo spiacenti, hai ragione. Aggiungendo una vista da Iterable [B] => A sarebbe quindi l'unica soluzione qui. – drexin

Problemi correlati