2015-04-29 4 views
3

Учитывая следующий макрос (спасибо @TravisBrown для этого help):Использование макросов сделать Case класса

JetDim.scala

case class JetDim(dimension: Int) { 
    require(dimension > 0) 
} 

object JetDim { 
    def validate(dimension: Int): Int = macro JetDimMacro.apply 
    def build(dimension: Int): JetDim = JetDim(validate(dimension)) 
} 

JetDimMacro.scala

import reflect.macros.Context 

object JetDimMacro { 

    sealed trait PosIntCheckResult 
    case class LteqZero(x: Int) extends PosIntCheckResult 
    case object NotConstant extends PosIntCheckResult 

    def apply(c: Context)(dimension: c.Expr[Int]): c.Expr[Int] = { 

     import c.universe._ 

     getInt(c)(dimension) match { 
      case Right(_)   => reify { dimension.splice } 
      case Left(LteqZero(x)) => c.abort(c.enclosingPosition, s"$x must be > 0.") 
      case Left(NotConstant) => reify { dimension.splice } 
     } 
    } 

    def getInt(c: Context)(dimension: c.Expr[Int]): Either[PosIntCheckResult, Int] = { 

     import c.universe._ 

     dimension.tree match { 
      case Literal(Constant(x: Int)) => if (x > 0) Right(x) else Left(LteqZero(x)) 
      case _       => Left(NotConstant) 
     } 
    } 
} 

It работы от REPL:

scala> import spire.math.JetDim 
import spire.math.JetDim 

scala> JetDim.validate(-55) 
<console>:9: error: -55 must be > 0. 
       JetDim.validate(-55) 
          ^

scala> JetDim.validate(100) 
res1: Int = 100 

Но я хотел бы построить это время компиляции проверку (через JetDimMacro) в apply метод случае класса.

Покушение 1

case class JetDim(dimension: Int) { 
    require(dimension > 0) 
} 

object JetDim { 
    private def validate(dimension: Int): Int = macro JetDimMacro.apply 
    def build(dimension: Int): JetDim = JetDim(validate(dimension)) 
} 

Но это не удалось:

scala> import spire.math.JetDim 
import spire.math.JetDim 

scala> JetDim.build(-55) 
java.lang.IllegalArgumentException: requirement failed 
    at scala.Predef$.require(Predef.scala:207) 
    at spire.math.JetDim.<init>(Jet.scala:21) 
    at spire.math.JetDim$.build(Jet.scala:26) 
    ... 43 elided 

Покушение 2

class JetDim(dim: Int) { 
    require(dim > 0) 

    def dimension: Int = dim 
} 

object JetDim { 
    private def validate(dimension: Int): Int = macro JetDimMacro.apply 
    def apply(dimension: Int): JetDim = { 
    validate(dimension) 
    new JetDim(dimension) 
    } 
} 

Тем не менее, что тоже не получилось:

scala> import spire.math.JetDim 
import spire.math.JetDim 

scala> JetDim(555) 
res0: spire.math.JetDim = [email protected] 

scala> JetDim(-555) 
java.lang.IllegalArgumentException: requirement failed 
    at scala.Predef$.require(Predef.scala:207) 
    at spire.math.JetDim.<init>(Jet.scala:21) 
    at spire.math.JetDim$.apply(Jet.scala:30) 
    ... 43 elided 

Я думал изменить JetDimMacro#apply, чтобы вернуть JetDim, а не Int. Однако JetDim живет в проекте core, который, как я вижу, зависит от проекта macros (где живет JetDimMacro).

Как использовать этот метод validate из сопутствующего объекта JetDim, чтобы проверить положительный int во время компиляции?

ответ

1

Проблема заключается в том, что к тому времени мы называем validate в apply мы уже имеем дело не с постоянной (типа одноэлементных). Итак, validate получает непостоянный Int.

В качестве альтернативы вы можете попробовать использовать неявный свидетель для позитивных целых чисел, который затем принимает JetDim в качестве конструктора. Например, что-то вроде:

package com.example 

case class JetDim(n: PositiveInt) 

case class PositiveInt(value: Int) { 
    require(value > 0) 
} 

Затем мы добавим неявное (макро) преобразование из Int => PositiveInt, что делает чек.

import scala.language.experimental.macros 

import scala.reflect.macros.blackbox.Context 

object PositiveInt { 
    implicit def wrapConstantInt(n: Int): PositiveInt = macro verifyPositiveInt 

    def verifyPositiveInt(c: Context)(n: c.Expr[Int]): c.Expr[PositiveInt] = { 
    import c.universe._ 

    val tree = n.tree match { 
     case Literal(Constant(x: Int)) if x > 0 => 
     q"_root_.com.example.PositiveInt($n)" 
     case Literal(Constant(x: Int)) => 
     c.abort(c.enclosingPosition, s"$x <= 0") 
     case x => 
     c.abort(c.enclosingPosition, s"cannot verify $x > 0") 
    } 
    c.Expr(tree) 
    } 
} 

Вы можете использовать JetDim(12), который будет проходить, или JetDim(-12), который потерпит неудачу (макро расширяет Int к PositiveInt).

+0

спасибо. При таком подходе объект-компаньон «PositiveInt», содержащий макрос impl, будет жить в том же классе, что и «JetDim», нет? Причина, по которой я спрашиваю, - это если мы хотим использовать «PositiveInt» в другом месте. –

+1

@KevinMeredith Да, это было бы полезно в других местах, поэтому было бы разумно, если бы он жил сам по себе, поэтому другие методы/классы могут его использовать. – tixxit