Returning original collection type in generic method
I think Miles Sabin solution is way too complex. Scala's collection already have the necessary machinery to make it work, with a very small change:
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)
}
How about using 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)
Your problem is that when viewed as a method on GenTraversable[A]
(which I'll use instead of Traversable[A]
in this answer) the result type of the filter
method is no more precise than GenTraversable[A]
. Unfortunately within the body of the multiMinBy
method as written that's all you know about xs
.
To get the result you're after you'll have to make the signature of multiMinBy
more precise. One way of doing this while still leaving the container type relatively open is to use a structural type as follows,
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)
}
The structural type HomFilter
allows us to assert that the argument to multiMinBy
must have a filter
method with the desired result type.
Sample REPL session,
scala> val mmb = multiMinBy(List("zza","zzza","zzb","zzzb"))(_.last)
mmb: List[String] = List(zza, zzza)
Bear in mind that this is a stricter requirement than that the container just be Traversable
: it's permissible for subtypes of GenTraversable
to define filter
methods which aren't regular in this way. The signature above will statically prevent values of such types from being passed to multiMinBy
... presumably that's the behaviour you're after.