需要类类型,但找到E(scala宏)

时间:2018-01-14 11:31:46

标签: scala types macros

我试图删除我正在编写的API中的一些样板文件。

粗略地说,我的API目前看起来像这样:

def toEither[E <: WrapperBase](priority: Int)(implicit factory: (String, Int) => E): Either[E, T] = {
  val either: Either[String, T] = generateEither()
  either.left.map(s => factory(s, priority))
}

这意味着用户必须为使用的每个E生成一个隐式工厂。我希望用一个宏来替换它,如果用户提供的类型没有正确的ctor参数,那么会产生很好的编译错误。

我有以下内容:

object GenericFactory {
  def create[T](ctorParams: Any*): T = macro createMacro[T]

  def createMacro[T](c: blackbox.Context)(ctorParams: c.Expr[Any]*)(implicit wtt: WeakTypeType[T]): c.Expr[T] = {
    import c.universe._
    c.Expr[T](q"new $wtt(..$ctorParams)")
  }
}

如果我为此GenericFactory.create[String]("hey")提供了真实的类型,我没有任何问题,但如果我提供通用类型:GenericFactory.create[E]("hey"),那么我会收到以下编译错误:class type required by E found

我哪里出错了?或者,如果我不想要的是什么,我还能做些什么来减少用户的工作量?

1 个答案:

答案 0 :(得分:1)

很抱歉,但我认为你不能让它发挥作用。问题是Scala(作为Java)使用类型擦除。这意味着所有泛型类型只有一种类型(可能除了现在不重要的值类型特化)。这意味着宏仅针对所有E展开一次,而不是针对用户提供的每个E专业化进行一次展开。并且没有办法表达一种限制,即某些泛型类型E必须具有带给定签名的构造函数(如果存在 - 您首先不需要宏)。显然它无法工作,因为编译器无法为泛型类型E生成构造函数调用。所以编译器说的是,为了生成构造函数调用,它需要一个真正的类而不是泛型E

换句话说,宏不是一个神奇的工具。使用宏只是在编译器处理的早期重写一段代码的一种方法,但随后它将由编译器以通常的方式处理。你的宏做的是重写

GenericFactory.create[E]("hey")

类似

new E("hey")

如果你只是在你的代码中写下它,你会得到同样的错误(并且可能不会感到惊讶)。

我认为你不能避免使用你的隐含工厂。您可能可以修改宏来生成有效类型的隐式工厂,但我认为您不能进一步改进代码。

更新:隐式工厂和宏

如果你只有一个地方需要一种类型的构造函数,我认为你能做到的最好(或者说我能做的最好)是:

旁注整个想法来自"Implicit macros" article

  1. 您定义StringIntCtor[T]类型类特征和将生成它的宏:
  2. import scala.language.experimental.macros
    import scala.reflect.macros._
    
    
    trait StringIntCtor[T] {
      def create(s: String, i: Int): T
    }
    
    
    object StringIntCtor {
      implicit def implicitCtor[T]: StringIntCtor[T] = macro createMacro[T]
    
    
      def createMacro[T](c: blackbox.Context)(implicit wtt: c.WeakTypeTag[T]): c.Expr[StringIntCtor[T]] = {
        import c.universe._
        val targetTypes = List(typeOf[String], typeOf[Int])
        def testCtor(ctor: MethodSymbol): Boolean = {
          if (ctor.paramLists.size != 1)
            false
          else {
            val types = ctor.paramLists(0).map(sym => sym.typeSignature)
            (targetTypes.size == types.size) && targetTypes.zip(types).forall(tp => tp._1 =:= tp._2)
          }
        }
    
        val ctors = wtt.tpe.decl(c.universe.TermName("<init>"))
        if (!ctors.asTerm.alternatives.exists(sym => testCtor(sym.asMethod))) {
          c.abort(c.enclosingPosition, s"Type ${wtt.tpe} has no constructor with signature <init>${targetTypes.mkString("(", ", ", ")")}")
        }
    
    
        // Note that using fully qualified names for all types except imported by default are important here
        val res = c.Expr[StringIntCtor[T]](
          q"""
               (new so.macros.StringIntCtor[$wtt] {
                   override def create(s:String, i: Int): $wtt = new $wtt(s, i)
                 })
           """)
    
        //println(res) // log the macro
        res
    
    
      }
    }
    
    1. 您将该特性用作
    2. class WrapperBase(val s: String, val i: Int)
      
      case class WrapperChildGood(override val s: String, override val i: Int, val float: Float) extends WrapperBase(s, i) {
        def this(s: String, i: Int) = this(s, i, 0f)
      }
      
      case class WrapperChildBad(override val s: String, override val i: Int, val float: Float) extends WrapperBase(s, i) {
      }
      
      object EitherHelper {
        type T = String
      
        import scala.util._
      
        val rnd = new Random(1)
      
        def generateEither(): Either[String, T] = {
          if (rnd.nextBoolean()) {
            Left("left")
          }
          else {
            Right("right")
          }
        }
      
      
        def toEither[E <: WrapperBase](priority: Int)(implicit factory: StringIntCtor[E]): Either[E, T] = {
          val either: Either[String, T] = generateEither()
          either.left.map(s => factory.create(s, priority))
        }
      }
      

      现在你可以这样做:

      val x1 = EitherHelper.toEither[WrapperChildGood](1)
      println(s"x1 = $x1")
      val x2 = EitherHelper.toEither[WrapperChildGood](2)
      println(s"x2 = $x2")
      //val bad = EitherHelper.toEither[WrapperChildBad](3) // compilation error generated by c.abort
      

      它会打印

        

      x1 =左(WrapperChildGood(左,1,0.0))   
      x2 =右(右)

      如果你想要确保存在不同构造函数的许多不同的地方,你需要使宏更复杂,以生成具有从外部传递的任意签名的构造函数调用。