Frage

I make extensive use of the Pimp my Library pattern, and I'd like to remove the boilerplate. For example, say I have some trait PrettyPrint:

trait PrettyPrint { def prettyPrint: String }

If I want to pimp Int and Double, I need to write code like this:

implicit def int2PrettyPrint(self: Int) = 
  new PrettyPrint { def prettyPrint = "Int: " + self }
implicit def double2PrettyPrint(self: Double) = 
  new PrettyPrint { def prettyPrint = "Double: " + self }

In the above, I'd classify as boilerplate: 1) The name of the implicit conversion, 2) The "new" keyword, 3) Perhaps the argument name "self", 4) Perhaps the "implicit" keyword. I'd rather write something like this:

@pimp[Int, PrettyPrint] { def prettyPrint = "Int: " + self }
@pimp[Double, PrettyPrint] { def prettyPrint = "Double: " + self }

On the right hand sides of the above code, the name "self" is assumed to be the conversion argument.

Ideas on how to do this?

Some notes:

1) I'm amenable to using Scala 2.10 if necessary.

2) The new implicit classes in Scala 2.10 don't suffice as far as I can tell. This is because there is only one implicit conversion for each implicit class. In other words, code like the following wouldn't compile because PrettyPrint is declared twice:

implicit class PrettyPrint(self: Int) = ...
implicit class PrettyPrint(self: Double) = ...
War es hilfreich?

Lösung 3

Follow-up on our discussion on NativeLibs4Java's mailing list, where I gave an example of such a compiler plugin (which expands @extend(Int) def foo = blah into implicit class foo(self: Int) extends AnyVal { def foo = blah }).

I've written a more elaborated plugin that expands these definitions into... macros (giving macro-expandable extensions / "pimps", with no runtime dependency!).

Given:

@extend(Any) def quoted(quote: String): String = quote + self + quote

It expands to:

import scala.language.experimental.macros
implicit class scalaxy$extensions$quoted$1(self: Any) {
  def quoted(quote: String) = macro scalaxy$extensions$quoted$1.quoted
}
object scalaxy$extensions$quoted$1 {
  def quoted(c: scala.reflect.macros.Context)
            (quote: c.Expr[String]): c.Expr[String] = {
    import c.universe._
    val Apply(_, List(selfTree$1)) = c.prefix.tree
    val self = c.Expr[Any](selfTree$1)
    {
      reify(quote.splice + self.splice + quote.splice)
    }
  }
}

Andere Tipps

You could just name your implicit classes differently:

implicit class PrettyPrintInt(self: Int) = ...
implicit class PrettyPrintDouble(self: Double) = ...

Here is another solution that requires signiicantly more boilerplate up front, in exchange for slightly less clutter for each specific instanciation of PrettyPrint:

implicit class PrettyPrintable[T]( val self: T ) extends AnyVal { 
  def prettyPrint( implicit impl: PrettyPrint[T]): String = impl.prettyPrint( self ) 
}
trait PrettyPrint[T]{ def prettyPrint( self: T ): String }
object PrettyPrint {
  def apply[T]( impl: T => String ): PrettyPrint[T] = new PrettyPrint[T] {
    def prettyPrint( self: T ) = impl( self )
  }
}

implicit val int2PrettyPrint = PrettyPrint[Int]( "Int: " + _ )
implicit val double2PrettyPrint = PrettyPrint[Double]( "Double: " + _ )
// Or more explicitly:
//implicit val int2PrettyPrint = PrettyPrint{self: Int => "Int: " + self }
//implicit val double2PrettyPrint = PrettyPrint{self: Double => "Double: " + self }

Compare:

implicit def int2PrettyPrint(self: Int) = new PrettyPrint { def prettyPrint = "Int: " + self } 

to:

implicit val int2PrettyPrint = PrettyPrint[Int]( "Int: " + _ )

You still need the implicit keyword though, as well as a unique name for the implicit value

Summary after 1 week: It appears I need to write a compiler plugin to get the exact behavior I specified.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top