Search code examples
scalagenericsimplicit

Implicit scala class with generics


I have this code:

object Peek {
    implicit def traversableToPeek[A](underlying: Traversable[A]) = new Peek(underlying)
}
class Peek[A](underlying: Traversable[A]) {

    /**
     * Java-style peek method
     */
    def peek(func: A => Unit): Traversable[A] = {
        underlying.foreach(func)
        underlying
    }
}

while lets me write things like List(1,2,3).peek(println).map(_+1).peek(println) which prints 1,2,3 and then 2,3,4 (after import Peek._)

However, the compile-time value of that expression is Traversable[Int]. This means that while this code compiles:

val l = List(1,2,3)
val l2 = 4 :: l

this code does not:

val l = List(1,2,3).peek(println)
val l2 = 4 :: l

since :: is not defined on Traversable. The intent of peek is to allow side-effect only operations without requiring somewhat ugly constructs like l.map(e => println(e); e)

I tried adding a second generic parameter T <: Traversable or T[A] <: Traversable[A] but could not get it compile in a manner where the implicit conversion worked.

I feel this should be possible, but I am not familiar enough with Scala to get the syntax correct.


Solution

  • The second generic parameter is the correct approach:

    implicit class Peek[C[X] <: Traversable[X], A](underlying: C[A]) {
      def peek(func: A => Unit): C[A] = {
        underlying.foreach(func)
        underlying
      }
    }
    

    (in Scala 2.13, replace deprecated Traversable with Iterable).

    In case you have a type extending Traversable[Something] which isn't itself generic, the above won't work, but

    implicit class Peek[C <: Traversable[A], A](underlying: C with Traversable[A]) {
      def peek(func: A => Unit): C = {
        underlying.foreach(func)
        underlying
      }
    }
    

    should.