2010-10-09 14 views
10

he llegado hasta aquí:¿Cómo escribir un método zipWith que devuelve el mismo tipo de colección que los que se le pasaron?

implicit def collectionExtras[A](xs: Iterable[A]) = new { 
    def zipWith[B, C, That](ys: Iterable[B])(f: (A, B) => C)(implicit cbf: CanBuildFrom[Iterable[A], C, That]) = { 
    val builder = cbf(xs.repr) 
    val (i, j) = (xs.iterator, ys.iterator) 
    while(i.hasNext && j.hasNext) { 
     builder += f(i.next, j.next) 
    } 
    builder.result 
    } 
} 
// collectionExtras: [A](xs: Iterable[A])java.lang.Object{def zipWith[B,C,That](ys: Iterable[B])(f: (A, B) => C)(implicit cbf: scala.collection.generic.CanBuildFrom[Iterable[A],C,That]): That} 

Vector(2, 2, 2).zipWith(Vector(4, 4, 4))(_ * _) 
// res3: Iterable[Int] = Vector(8, 8, 8) 

Ahora el problema es que por encima de método siempre devuelve un Iterable. ¿Cómo hago para que devuelva la colección de tipos que se le pasaron? (en este caso, Vector) Gracias.

+0

'new {def foo =}' produce un valor de tipo estructural, que se invoca mediante reflexión; para evitar esto, declare las firmas en un rasgo ZipWith y devuelva una instancia de este rasgo. Esto se aplica a la pregunta y a todas las soluciones. – Blaisorblade

Respuesta

9

Se acercó lo suficiente. Sólo un cambio menor en dos líneas:

implicit def collectionExtras[A, CC[A] <: IterableLike[A, CC[A]]](xs: CC[A]) = new { 
    def zipWith[B, C, That](ys: Iterable[B])(f: (A, B) => C)(implicit cbf: CanBuildFrom[CC[A], C, That]) = { 
    val builder = cbf(xs.repr) 
    val (i, j) = (xs.iterator, ys.iterator) 
    while(i.hasNext && j.hasNext) { 
     builder += f(i.next, j.next) 
    } 
    builder.result 
    } 
} 

primer lugar, usted tiene que conseguir el tipo de colección que se pasa, por lo que añadió CC[A] como un parámetro de tipo. Además, esa colección debe ser capaz de "reproducirse", eso está garantizado por el segundo parámetro de tipo IterableLike - así que CC[A] <: IterableLike[A, CC[A]]. Tenga en cuenta que este segundo parámetro de IterableLike es Repr, precisamente el tipo de xs.repr.

Naturalmente, CanBuildFrom necesita recibir CC[A] en lugar de Iterable[A]. Y eso es todo lo que hay que hacer.

Y el resultado:

scala> Vector(2, 2, 2).zipWith(Vector(4, 4, 4))(_ * _) 
res0: scala.collection.immutable.Vector[Int] = Vector(8, 8, 8) 
+0

bajando a 'hasNext' y todo eso significa que no funciona en infinitos flujos. La solución de huynhjl sí. –

+0

Tengo dos problemas con tu respuesta: a) escribes 'CC [A]', lo cual es problemático al comprimir listas (hay una razón por la cual la biblioteca de colecciones Scala no usa tipos de clasificación más alta de esta manera, y estoy seguro ya sabes). b) También esta respuesta hace que el tipo de devolución 'collectionExtras' sea un tipo estructural (y por lo tanto ineficaz). – Blaisorblade

+0

@Blaisorblade si mira mi comentario a axel22, en el momento en que la otra solución no se me ocurrió. En cuanto al tipo estructural, no veo cómo se vuelve: los tipos estructurales pueden ser difíciles de esa manera. : -/ –

8

El problema anterior es que su conversión implícita collectionExtras hace que el objeto obtenido de perder información de tipo. En particular, en la solución anterior, el tipo de colección de hormigón se pierde porque se le pasa un objeto del tipo Iterable[A] - a partir de este momento, el compilador ya no conoce el tipo real de xs. Aunque la fábrica de constructores CanBuildFrom asegura programáticamente que el tipo dinámico de la colección es correcto (realmente obtienes un Vector), estáticamente, el compilador solo sabe que zipWith devuelve algo que es un Iterable.

Para resolver este problema, en lugar de tener la conversión implícita tomar un Iterable[A], que tome un IterableLike[A, Repr]. ¿Por qué?

Iterable[A] normalmente se declara como algo parecido a:

Iterable[A] extends IterableLike[A, Iterable[A]] 

La diferencia con Iterable es que este IterableLike[A, Repr] mantiene el tipo de colección concreta como Repr. La mayoría de las colecciones de hormigón, además de la mezcla en Iterable[A], también mezclan en el rasgo IterableLike[A, Repr], en sustitución del Repr con su tipo de hormigón, como a continuación:

Vector[A] extends Iterable[A] with IterableLike[A, Vector[A]] 

que pueden hacer esto porque Repr tipo de parámetro se declara como covariante.

Para resumir, utilizando IterableLike causas que la conversión implícita para mantener la información de tipo de recogida de concreto (es decir Repr) alrededor y utilizarlo cuando se define zipWith - tenga en cuenta que la fábrica constructor CanBuildFrom contendrá ahora Repr en lugar de Iterable[A] para el primer parámetro tipo, haciendo que el objeto implícito adecuado para ser resuelto:

import collection._ 
import collection.generic._ 

implicit def collectionExtras[A, Repr](xs: IterableLike[A, Repr]) = new { 
    def zipWith[B, C, That](ys: Iterable[B])(f: (A, B) => C)(implicit cbf: CanBuildFrom[Repr, C, That]) = { 
    val builder = cbf(xs.repr) 
    val (i, j) = (xs.iterator, ys.iterator) 
    while(i.hasNext && j.hasNext) { 
     builder += f(i.next, j.next) 
    } 
    builder.result 
    } 
} 

la lectura de su formulación de las preguntas con más cuidado ("¿Cómo escribir un método zipWith que devuelve el mismo tipo de colección como los que se le pasa"), me parece que quieres tener el mismo tipo de recolección iones como los pasados ​​a zipWith, no a la conversión implícita, que es del mismo tipo que ys.

mismas razones que antes, ver más abajo solución:

import collection._ 
import collection.generic._ 

implicit def collectionExtras[A](xs: Iterable[A]) = new { 
    def zipWith[B, C, That, Repr](ys: IterableLike[B, Repr])(f: (A, B) => C)(implicit cbf: CanBuildFrom[Repr, C, That]) = { 
    val builder = cbf(ys.repr) 
    val (i, j) = (xs.iterator, ys.iterator) 
    while(i.hasNext && j.hasNext) { 
     builder += f(i.next, j.next) 
    } 
    builder.result 
    } 
} 

Con los resultados:

scala> immutable.Vector(2, 2, 2).zipWith(mutable.ArrayBuffer(4, 4, 4))(_ * _) 
res1: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(8, 8, 8) 
+1

Interesante. Nunca se me ocurrió obtener un 'IterableLike [_, Repr]' y parametrizar en 'Repr'. –

+0

Supongo que una ventaja podría ser en caso de que 'Repr' sea un' String', o algún otro tipo no parametrizado, como en 'StringOps'. – axel22

+0

bajando a 'hasNext' y todo eso significa que no funciona en infinitos flujos. La solución de huynhjl sí. –

5

Para ser honesto, no estoy seguro de lo que realmente funciona:

implicit def collectionExtras[CC[X] <: Iterable[X], A](xs: CC[A]) = new { 
    import collection.generic.CanBuildFrom 
    def zipWith[B, C](ys: Iterable[B])(f: (A, B) => C) 
    (implicit cbf:CanBuildFrom[Nothing, C, CC[C]]): CC[C] = { 
    xs.zip(ys).map(f.tupled)(collection.breakOut) 
    } 
} 

scala> Vector(2, 2, 2).zipWith(Vector(4, 4, 4))(_ * _) 
res1: scala.collection.immutable.Vector[Int] = Vector(8, 8, 8) 

¡Como que mono parcheé this answer from retronym hasta que funcionó!

Básicamente, quiero utilizar el CC[X] constructor de tipo para indicar que zipWith debe devolver el tipo de colección de xs pero con C como el parámetro de tipo (CC[C]). Y quiero usar breakOut para obtener el tipo de resultado correcto. En cierto modo me esperaba que hubo un CanBuildFrom implícita en su alcance, pero luego se puso este mensaje de error:

required: scala.collection.generic.CanBuildFrom[Iterable[(A, B)],C,CC[C]] 

El truco era entonces para usar en lugar de NothingIterable[(A, B)]. Supongo que implícito se define en alguna parte ...

Además, me gusta pensar en su zipWith como zip y luego map, así que cambié la aplicación. Aquí está con su aplicación:

implicit def collectionExtras[CC[X] <: Iterable[X], A](xs: CC[A]) = new { 
    import collection.generic.CanBuildFrom 
    def zipWith[B, C](ys: Iterable[B])(f: (A, B) => C) 
    (implicit cbf:CanBuildFrom[Nothing, C, CC[C]]) : CC[C] = { 
    val builder = cbf() 
    val (i, j) = (xs.iterator, ys.iterator) 
    while(i.hasNext && j.hasNext) { 
     builder += f(i.next, j.next) 
    } 
    builder.result 
    } 
} 

Nota this article proporciona algunos antecedentes sobre el patrón de tipo constructor.

+0

si usa zip y mapa en lugar de bajar al nivel de iterador, entonces funciona con infinitos flujos. +1! –

+0

funciona con 'clase implícita' en Scala 2.10! ver http://gist.github.com/2942990 –

Cuestiones relacionadas