How can you can write generic Scala enhancement methods that bind collection type as well as element type?
Asked Answered
E

1

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

Evangelinaevangeline answered 4/9, 2015 at 18:15 Comment(0)
E
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)
Evangelinaevangeline answered 4/9, 2015 at 18:15 Comment(3)
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 Spurt
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.