6

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].

eje
  • 945
  • 11
  • 22

1 Answers1

7

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)
eje
  • 945
  • 11
  • 22
  • 4
    Also you could define class as `implicit class Enhance[T, S[E] <: IterableLike[E, S[E]]](seq: S[T]) {` and then you will not need `asInstanceOf ` – Николай Митропольский Sep 04 '15 at 19:38
  • 3
    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. – Kolmar Sep 04 '15 at 19:52
  • 1
    Thanks @НиколайМитропольский and Kolmar, both of those modification are very useful! I reworked my original answer to show them in action. – eje Sep 04 '15 at 23:24