[英]Class type required but E found (scala macro)
我正在尝试删除我正在编写的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
。
我哪里出问题了? 或者如果我想要的东西不可能实现,那么我还能做些其他事情来减少用户的工作量吗?
抱歉,但我认为您无法使其正常运行。 问题是Scala(作为Java)使用类型擦除。 这意味着所有泛型类型只有一种类型(可能除了价值类型专长之外,这现在已经不重要了)。 这意味着该宏对于所有E
仅扩展一次,而对于用户提供的每个E
专业化则仅扩展一次。 而且,没有办法表达出某种限制,即某些通用类型E
必须具有带有给定签名的构造函数(如果有,则一开始就不需要宏)。 因此,显然它无法工作,因为编译器无法为通用类型E
生成构造函数调用。 因此,编译器说的是,要生成构造函数调用,它需要一个实型而不是通用E
换句话说,宏不是魔术工具。 使用宏只是在编译器处理的早期重新编写一段代码的一种方法,但是随后它将由编译器以通常的方式进行处理。 而您的宏所做的就是重写
GenericFactory.create[E]("hey")
用类似的东西
new E("hey")
如果仅在代码中编写该代码,则会得到相同的错误(可能不会感到惊讶)。
我认为您无法避免使用隐式工厂。 您可能可以修改宏以为有效类型生成那些隐式工厂,但我认为您无法进一步改进代码。
更新:隐式工厂和宏
如果您只有一个地方需要一种类型的构造函数,那么我认为您可以做到的最好(或者是我可以做到的最好):
旁注 :整个想法来自“隐式宏”文章
StringIntCtor[T]
类型类特征和将生成该特征的宏: 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
}
}
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 =右(右)
如果要在许多不同的地方确保存在不同的构造函数,则需要使宏复杂得多,以生成具有从外部传递的任意签名的构造函数调用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.