Reputation: 3602
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) = ...
Upvotes: 2
Views: 1019
Reputation: 1778
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)
}
}
}
Upvotes: 2
Reputation: 3602
Summary after 1 week: It appears I need to write a compiler plugin to get the exact behavior I specified.
Upvotes: 1
Reputation: 32719
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
Upvotes: 3
Reputation: 170723
You could just name your implicit classes differently:
implicit class PrettyPrintInt(self: Int) = ...
implicit class PrettyPrintDouble(self: Double) = ...
Upvotes: 3