Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using private constructor in a macro

I want to use a private constructor in a macro. This example is a positive integer, but the basic pattern could not only be used for other numeric types like even numbers, but also string derived types like email addresses or a directory name. By making the constructor private the user is denied the opportunity to make an illegal type. I have the following code:

object PosInt
{
  import language.experimental.macros 
  import reflect.runtime.universe._
  import reflect.macros.Context

  def op(inp: Int): Option[PosInt] = if (inp > 0) Some(new PosInt(inp)) else None

  def apply(param: Int): PosInt = macro apply_impl

  def apply_impl(c: Context)(param: c.Expr[Int]): c.Expr[PosInt] =
  {
    import c.universe._
    param match {
      case Expr(Literal(i)) if (i.value.asInstanceOf[Int] > 0) =>
      case Expr(Literal(i)) if (i.value.asInstanceOf[Int] == 0) => c.abort(c.enclosingPosition, "0 is not a positive integer") 
      case Expr(Literal(i)) => c.abort(c.enclosingPosition, "is not a positive integer")      
      case _ => c.abort(c.enclosingPosition, "Not a Literal")
    }    
    reify{new PosInt(param.splice)}    
  }  
}

class PosInt (val value: Int) extends AnyVal

However if I make the PosInt Constructor private, although the Macro compiles as expected I get an error if try to use the macro. I can't work out how to build the expression tree manually, but I'm not sure if that would help anyway. Is there anyway I can do this?

You still can't use a private constructor even if PosInt is not a value class. I'll accept an answer that doesn't use a value class. The disadvantage of value classes is that they get type erasure. Plus classes that I'm interested in like subsets of 2d co-ordinates can't be implement as value classes anyway. I'm not actually interested in Positive Integers, I'm just using them as a simple test bed. I'm using Scala 2.11M5. Scala 2.11 will have the addition of the quasiquotes feature. I haven't worked out how to use, quasiquotes yet, as all the material at the moment on them seems to assume a familiarity with Macro Paradise, which I don't have.

like image 274
Rich Oliver Avatar asked Oct 03 '13 22:10

Rich Oliver


1 Answers

Unfortunately for what you are trying to achieve, macros do not work this way. They just manipulate the AST at compile time. Whatever the final result is, it is always something you could have written literally in Scala (without the macro).

Thus, in order to constrain the possible values of PosInt, you will need a runtime check somewhere, either in a public constructor or in a factory method on the companion object.

If runtime exceptions are not palatable to you, then one possible approach would be:

  • Make the constructor private on the class.
  • Provide (for example) a create method on the companion object that returns Option[PosInt] (or Try[PosInt], or some other type of your choice that allows you to express a "failure" when the argument is out of range).
  • Provide an apply method on the companion object similar to your example, which verifies at compile time that the argument is in range and then returns an expression tree that simply calls create(x).get.

Calling .get on the Option is acceptable in this case because you are sure that it will never be None.

The downside is that you have to repeat the check twice: once at compile time, and once at runtime.

like image 98
Todd Owen Avatar answered Nov 28 '22 19:11

Todd Owen