If you are like me, you occasionally want to write enhanced methods for Scala collections or sequences, but you'd like to bind the collection type as well as the element type, not just upcast to Seq[T].
How can you can write generic Scala enhancement methods that bind collection type as well as element type?
There is a way to do it, and it works like this:
object enhance {
import scala.language.higherKinds
import scala.language.implicitConversions
import scala.collection.SeqLike
import scala.collection.generic.CanBuildFrom
implicit class Enhance[T, S[E] <: SeqLike[E, S[E]]](seq: S[T]) {
def first3(implicit cbf: CanBuildFrom[S[T], T, S[T]]) = seq.take(3)
def foo = seq.iterator
def goo(implicit cbf: CanBuildFrom[Nothing, T, S[T]]) = foo.take(3).to[S]
def moo[U](f: T => U)(implicit cbf: CanBuildFrom[S[T], U, S[U]]) = seq.map(f)
}
}
Using the type signature pattern above, the enhanced methods are aware of both the element type T
(e.g. Int
or String
) and the higher-kinded sequence type S
(e.g. List
or Vector
) and so it can return exactly the sequence type that it was called on.
Many sequence methods may require CanBuildFrom
implicits, which are added to the Enhance
methods as implicit parameters, where they are needed in the examples above.
Following is a sample run, showing the desired higher-kinded collection return types:
scala> import enhance._
import enhance._
scala> (1 to 10).toList.first3
res0: List[Int] = List(1, 2, 3)
scala> (1 to 10).toVector.first3
res1: Vector[Int] = Vector(1, 2, 3)
scala> (1 to 10).toList.goo
res2: List[Int] = List(1, 2, 3)
scala> (1 to 10).toVector.goo
res3: Vector[Int] = Vector(1, 2, 3)
scala> (1 to 10).toList.moo(_.toDouble)
res4: List[Double] = List(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0)
scala> (1 to 10).toVector.moo(_.toDouble)
res5: Vector[Double] = Vector(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0)
It won't work, if you use
map
or any other method that takes a CanBuildFrom
in new method implementations. The result would just come out as Seq
. You would need to pass a relevant implicit CanBuildFrom
to the Enhance
class or to the method. –
Ballenger Thanks @НиколайМитропольский and Kolmar, both of those modification are very useful! I reworked my original answer to show them in action. –
Evangelinaevangeline
© 2022 - 2024 — McMap. All rights reserved.
implicit class Enhance[T, S[E] <: IterableLike[E, S[E]]](seq: S[T]) {
and then you will not needasInstanceOf
– Spurt