In Scala, can I implicitly convert only certain literals to my custom type?
implicit-conversion, literals, macros, scala
Solution
This kind of compile-time checking are the good terrain to use macros, which will be available in 2.10
A very smart guy named Jason Zaugg has already implemented something similar to what you need, but it applies to regex: Regex Compile Time checking.
You might want to look to its Macrocosm to see how it is done and how you could code your own macros with the same purpose.
https://github.com/retronym/macrocosm
If you really want to know more about Macros, firstly I would say that you need to be brave because the documentation is scarce for now and the API is likely to change. Jason Zaugg works compiles fine with 2.10-M3 but I am not sure it will works with the newer version.
If you want to start with some readings:
A good entry point is the scalamacros website http://scalamacros.org/ and the SIP document https://docs.google.com/document/d/1O879Iz-567FzVb8kw6N5OBpei9dnbW0ZaT7-XNSa6Cs/edit?pli=1
If you have time, you might also wants to read Eugene Burmako presentation: http://scalamacros.org/talks/2012-04-28-MetaprogrammingInScala210.pdf
Now, getting to the topic, Scala macros are CATs : "Compile-time AST Transformations". The abstract syntax tree is the way the compiler represents your source code. The compiler applies consequent transformations to the AST and at the last step it actual generates the java bytecode.
Let's now look to Jason Zaugg code:
def regex(s: String): scala.util.matching.Regex = macro regexImpl
def regexImpl(c: Context)(s: c.Expr[String]): c.Expr[scala.util.matching.Regex] = {
import c.universe._
s.tree match {
case Literal(Constant(string: String)) =>
string.r // just to check
c.reify(s.splice.r)
}
}
As you seen regex is a special function which takes a String and returns a Regex, by calling macro regexImpl
A macro function receives a context in the first parameter lists, and in second argument list the parameters of the macro under the form of c.Expr[A] and returns a c.Expr[B]. Please note that c.Expr is a path dependent type, i.e. it is a class defined inside the Context, so that if you have two context the following is illegal
val c1: context1.Expr[String] = ...
val c2: context2.Expr[String] = ...
val c3: context1.Expr[String] = context2.Expr[String] // illegal , compile error
Now if you look what happens in the code:
- There is a match block which matches on s.tree
- If s.tree is a Literal, containing a constant String , string.r is called
What's going on here is that there is an implicit conversion from string to StringOps defined in Predef.scala, which is automatically imported in the compilation every scala source
`implicit def augmentString(x: String): StringOps = new StringOps(x)`
StringOps extends scala.collection.immutable.StringLike, which contains:
def r: Regex = new Regex(toString)
Since macros are executed at compile time, this will be executed at compile time, and compilation will fail if an exception will be thrown (that is the behaviour of creating a regex from an invalid regex string)
Note: unluckily the API is very unstable, if you look at http://scalamacros.org/documentation/reference.html you will see a broken link towards the Context.scala. The right link is https://github.com/scala/scala/blob/2.10.x/src/reflect/scala/reflect/makro/Context.scala
Problem
In my app, I am keeping track of the number of credits the user has. To add some type checking, I'm using a `Credits` class similar to this one: ``` case class Credits(val numCredits: Int) extends Ordered[Credits] { ... } ``` Suppose I have a function `def accept(creds: Credits): Unit` that I want to call. Would there be a way for me to call it with ``` process(Credits(100)) process(0) ``` but not with this? ``` process(10) ``` I.e., I'd like to provide an implicit conversion only from the literal `0` and none other. Right now, I just have `val Zero = Credits(0)` in the companion object and I think that's rather good practice, but I'd be interested in an answer anyway, including other comments, like: - could this be done with macro implicit conversions in 2.10? - should `Credits` rather extend AnyVal and not be a case class in 2.10?