2012-08-04 6 views
5

En esta función parametrizada, ¿por qué necesito el molde? ¿Y cómo puedo deshacerme de eso?En esta función parametrizada de Scala, ¿por qué necesito el modelo?

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

Si no tengo el molde al final, me sale este mensaje de error:

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

Esta función (con el elenco) funciona para todos los casos que he probado en , y sé por escribir cosas como lo siguiente en el REPL Scala que es capaz de inferir el tipo de resultado correctamente cuando no en el contexto de una función parametrizada:

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) 

por favor, explique!

+0

Pregunta similar que utiliza 'CanBuildFrom' para evitar el problema: [Función que toma genéricamente un tipo y devuelve el mismo tipo] (http://stackoverflow.com/questions/10019529/function-which-generically- takes- a-type-and-returns-the-same-type). No puedo hacerlo para trabajar con esta pregunta, ¿alguien más? – sschaef

+0

Obtuve CanBuildFrom para que funcionase para mi pregunta, y puse la solución en una respuesta. Vea la respuesta a continuación si tiene curiosidad. – Douglas

+0

¡Buena respuesta! Por cierto, puede aceptar sus propias respuestas ... – sschaef

Respuesta

4

Según algunas sugerencias en los comentarios, miré en CanBuildFrom, y esto es lo que ocurrió:

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

Y hay NO emitidos. Como tal, incluso funciona para Ranges.

Sin embargo, tener que comenzar con un retumbo vacío y luego usar "++ =" para llenarlo parece un poco poco elegante, así que si alguien tiene una solución más elegante, soy todo oídos.

Aquí hay otra función genérica que implementé que fue un poco más complicada que la anterior porque el tipo de devolución no es el mismo que el tipo de argumento. Es decir., La entrada es una secuencia de A 's, pero la salida es una secuencia de (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 
} 

Y aquí hay otro:

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

que todavía no tienen una comprensión profunda de los tipos "Me gusta" y CanBuildFrom, pero entiendo la esencia. Y es bastante fácil en la mayoría de los casos escribir la versión de fundición de una función genérica como primer paso, y luego agregar el modelo CanBuildFrom y IterableLike para hacer que la función sea más general y totalmente segura.

3

Hay algunos casos en los que collect no devuelve el mismo subtipo de Iterable como se le llamaba en, por ejemplo, en el caso de 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, por supuesto. Rangos estúpidos! ¿Hay algún otro rasgo que pueda usarse que excluya tales secuencias de mal comportamiento? ¿O debería simplemente vivir con el elenco? – Douglas

+0

Supongo que la forma correcta de hacerlo sería utilizar la magia CanBuildFrom utilizada en la API de recopilación? –

+0

Conseguí que CanBuildFrom funcionara para mi pregunta, y puse la solución en una respuesta junto a esta. – Douglas

1

El problema aquí es que, llamando collect en xs lo convierte a Iterable[B]. A <% Iterable[B] significa que A se puede ver como Iterable[B], lo que no significa necesariamente que Iterable[B] también se puede ver como A. Lo que realmente ocurre aquí es

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] 

Cuando tengo por ejemplo esto:

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

y llamo

everyNth(new Foo, 2) 

consigo

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

Usted debe evitar la proyección de aquí. O agrega una vista desde Iterable[B] => A

editar: el tipo encuadernado no funciona aquí.

+0

Reemplazar la vista enlazada con un tipo enlazado no elimina la necesidad de un molde aquí, así que no estoy precisamente claro en lo que está afirmando. – Douglas

+0

Lo sentimos, tienes razón. Agregar una vista desde Iterable [B] => A sería la única solución aquí. – drexin

Cuestiones relacionadas