Search code examples
scalaimplicit-conversionbuilderimplicitextending

Can I "Pimp my Library" on a parameterized trait with method returning this.type?


I want to extend my parameterized trait Field[T] with method returning this.type using Pimp my Library pattern. But I stuck with compiler errors. I tried some variants, but without success.

Am I doing it wrong? Or asking for the impossible?

Here is my test example:

trait Field[T]

class IntField extends Field[Int] {
  def getInt = 5
}

// Variant 1: return field.type
object Var1 {
  implicit class RichField[T](val field: Field[T]) {
    def bar(fn: T => String): field.type = {
      // some actions using T
      field
    }
  }

  new IntField().bar(_.toString).getInt  // Error: value getInt is not a member of Field[Int]
}

// Variant 2: use two type parameters
object Var2 {
  implicit class RichField[T, F <: Field[T]](val field: F) {
    def bar(fn: T => String): F = {
      // some actions using T
      field
    }
  }

  new IntField().bar(_.toString).getInt // <-- Error: value bar is not a member of IntField
}

// Variant 3: use higher kinds
object Var3 {
  import scala.language.higherKinds

  implicit class RichField[F[X] <: Field[X], T](val field: F[T]) {
    def bar(fn: T => String): F[T] = {
      // some actions using T
      field
    }
  }

  new IntField().bar(_.toString).getInt // <-- Error: value getInt is not a member of Field[Int]
}

Update: Parameter T is important for method bar, it cannot be ignored.


Solution

  • In variant 2, you are not using T anywhere. Just remove it and the compiler won't be confused anymore:

    implicit class RichField[F <: Field[_]](val field: F) {
      def bar: F = field
    }
    

    UPDATE: If you actually need T, as you mentioned in a comment, a possible workaround is this:

    implicit class RichField[T, F <: Field[_]](val field: F with Field[T]) {
      def bar(fn: T => String): F = {
        // some actions using T
        field
      }
    }
    

    And here's a proof that it works as expected (using toString as in your example is not a good test given that toString is available on every class, so even if the compiler inferred Any the code would have compiled):

    scala> case class Foo(name: String)
    defined class Foo
    
    scala> class FooField extends Field[Foo] {
         |   def getInt = 5
         | }
    defined class FooField
    
    scala> new FooField().bar(_.name).getInt
    res8: Int = 5