2011-11-22 8 views
16

decir que queremos hacer una función como minBy que devuelve todos los elementos de igual minimalismo en una colección:Volviendo tipo de colección original en método genérico

def multiMinBy[A, B: Ordering](xs: Traversable[A])(f: A => B) = { 
    val minVal = f(xs minBy f) 
    xs filter (f(_) == minVal) 
} 

scala> multiMinBy(List("zza","zzza","zzb","zzzb"))(_.last) 
res33: Traversable[java.lang.String] = List(zza, zzza) 

Hasta ahora, todo bien, excepto que tenemos una Traversable vuelta en lugar de nuestra inicial List.

así que he intentado cambiar la firma a

def multiMinBy[A, B: Ordering, C <: Traversable[A]](xs: C)(f: A => B) 

con la esperanza de que podría conseguir un C atrás en lugar de un Traversable[A]. Sin embargo, no recibo nada a cambio:

scala> multiMinBy(List("zza","zzza","zzb","zzzb"))(_.last) 

<console>:9: error: inferred type arguments [Nothing,Nothing,List[java.lang.String]] 
do not conform to method multiMinBy's type parameter bounds [A,B,C <: Traversable[A]] 

Creo que esto es porque tenemos C que aparecen en los argumentos antes A ha deducido? Así que me da la vuelta al orden de los argumentos, y añadí un reparto:

def multiMinBy[A, B: Ordering, C <: Traversable[A]](f: A => B)(xs: C) = { 
    val minVal = f(xs minBy f) 
    (xs filter (f(_) == minVal)).asInstanceOf[C] 
} 

que funciona, excepto que tenemos que llamarlo así:

multiMinBy((x: String) => x.last)(List("zza","zzza","zzb","zzzb")) 

¿Hay una manera de conservar la sintaxis original, mientras recuperas el tipo de colección correcto?

Respuesta

20

Creo que la solución de Miles Sabin es demasiado compleja. La colección de Scala ya tiene la maquinaria necesaria para hacerlo funcionar, con un cambio muy pequeño:

import scala.collection.TraversableLike 
def multiMinBy[A, B: Ordering, C <: Traversable[A]] 
       (xs: C with TraversableLike[A, C]) 
       (f: A => B): C = { 
    val minVal = f(xs minBy f) 
    xs filter (f(_) == minVal) 
} 
+3

Sí, estoy de acuerdo, esta es una solución mejor que la mía. –

10

Su problema es que cuando se ve como un método de GenTraversable[A] (que voy a utilizar en lugar de Traversable[A] en esta respuesta) el tipo de resultado del método filter hay más preciso que GenTraversable[A]. Desafortunadamente, dentro del cuerpo del método multiMinBy tal como está escrito, eso es todo lo que sabes sobre xs.

Para obtener el resultado que busca, deberá hacer que la firma de multiMinBy sea más precisa. Una forma de hacer esto al mismo tiempo dejando el tipo de contenedor relativamente abierto es el uso de un tipo estructural de la siguiente manera,

type HomFilter[CC[X] <: GenTraversable[X], A] = 
    CC[A] { def filter(p : A => Boolean) : CC[A] } 

def multiMinBy[CC[X] <: GenTraversable[X], A, B: Ordering] 
    (xs: HomFilter[CC, A])(f: A => B) : CC[A] = { 
    val minVal = f(xs minBy f) 
    xs filter (f(_) == minVal) 
    } 

El tipo estructural HomFilter nos permite afirmar que el argumento a multiMinBy debe tener un método filter con la deseada tipo de resultado

sesión REPL

la muestra,

scala> val mmb = multiMinBy(List("zza","zzza","zzb","zzzb"))(_.last) 
mmb: List[String] = List(zza, zzza) 

Tenga en cuenta que este es un requisito más estricto que el contenedor acaba de ser Traversable: es permisible para los subtipos de GenTraversable para definir filter métodos que no son regulares de esta manera . La firma de arriba evitará estáticamente que los valores de dichos tipos pasen al multiMinBy ... presumiblemente ese es el comportamiento que busca.

+0

Se puede evitar toda la cuestión de tipado estructural con el uso de 'TraversableLike'. –

13

¿Qué le parece usar CanBuildFrom?

import scala.collection.immutable._ 
import scala.collection.generic._ 

def multiMinBy[A, B, From[X] <: Traversable[X], To](xs: From[A])(f: A => B) 
    (implicit ord: Ordering[B], bf: CanBuildFrom[From[_], A, To]) = { 
    val minVal = f(xs minBy f) 
    val b = bf() 
    b ++= (xs filter (f(_) == minVal)) 
    b.result 
} 



scala> multiMinBy(List("zza","zzza","zzb","zzzb"))(_.last) 
res1: List[java.lang.String] = List(zza, zzza) 
Cuestiones relacionadas