我正在编写一个Scala隐式宏,它自动为case类生成一个类型类(使用quasiquote,Scala 2.10.3都带有宏天堂编译器插件和Scala 2.11.0-M7).
隐式宏以递归方式查找参数的类型类.
只要case类不接受类型参数或者在生成的代码中没有使用类型参数,它就可以正常工作.
但是一旦需要隐式值,
调用站点的编译就会失败,并且"找不到参数e的隐含值".
以下是重现问题的代码:
trait TestTypeClass[A] { def str(x: A): String } object Test { implicit def BooleanTest = new TestTypeClass[Boolean] { def str(x: Boolean) = x.toString } def CaseClassTestImpl[A: c.WeakTypeTag](c: Context): c.Expr[TestTypeClass[A]] = { import c.universe._ val aType = weakTypeOf[A] val TestTypeClassType = weakTypeOf[TestTypeClass[_]] val typeName = aType.typeSymbol.name.decoded val params = aType.declarations.collectFirst { case m: MethodSymbol if m.isPrimaryConstructor => m }.get.paramss.head val paramTypes = aType.declarations.collectFirst { case m: MethodSymbol if m.isPrimaryConstructor => m }.get.paramss.head.map(_.typeSignature) val paramList = for (i <- 0 until params.size) yield { val param = params(i) val paramType = paramTypes(i) val paramName = param.name.decoded q"str($param)" } println(paramList) val src = q""" new TestTypeClass[$aType] { def str(x: $aType) = Seq(..$paramList).mkString(",") } """ c.Expr[TestTypeClass[A]](src) } implicit def CaseClassTest[A]: TestTypeClass[A] = macro CaseClassTestImpl[A] def str[A: TestTypeClass](x: A) = implicitly[TestTypeClass[A]].str(x) } // somewhere in other module implicitly[TestTypeClass[TestClass]] // OK. implicitly[TestTypeClass[TestClass2[Boolean]]] // Error // could not find implicit value for parameter e: TestTypeClass[TestClass2[Boolean]] implicitly[TestTypeClass[TestClass2[TestClass]]] // Error // could not find implicit value for parameter e: TestTypeClass[TestClass2[TestClass]]
它是如此设计,我做错了什么,还是编译错误?
有一些应该避免你的版本从工作表面层次的问题在所有的,但一旦他们解决,你应该能够做到你想要什么(它是否是一个好主意,那是另一问题,一个,我会试着在这个答案的最后解决).
这三个最大的问题是:
q"str($param)"
首先,在生成的代码的上下文中,str
将引用您正在定义和实例化的匿名类的str
方法,而不是方法on Test
.接下来,这将生成看起来像str(member)
,但member
在生成的代码的上下文中不会有任何意义的代码 - 您想要类似的东西str(x.member)
.最后(并且相关地),每个param
都将是构造函数参数,而不是访问器.
以下是一个完整的工作示例(在2.10.3上测试):
import scala.language.experimental.macros import scala.reflect.macros.Context trait TestTypeClass[A] { def str(x: A): String } object Test { implicit def BooleanTest = new TestTypeClass[Boolean] { def str(x: Boolean) = x.toString } def CaseClassTestImpl[A: c.WeakTypeTag]( c: Context ): c.Expr[TestTypeClass[A]] = { import c.universe._ val aType = weakTypeOf[A] val params = aType.declarations.collect { case m: MethodSymbol if m.isCaseAccessor => m }.toList val paramList = params.map(param => q"Test.str(x.$param)") val src = q""" new TestTypeClass[$aType] { def str(x: $aType) = Seq(..$paramList).mkString(",") } """ c.Expr[TestTypeClass[A]](src) } implicit def CaseClassTest[A]: TestTypeClass[A] = macro CaseClassTestImpl[A] def str[A: TestTypeClass](x: A) = implicitly[TestTypeClass[A]].str(x) }
然后是一些演示设置:
import Test._ case class Foo(x: Boolean, y: Boolean) case class Bar[A](a: A)
最后:
scala> str(Bar(Foo(true, false))) res0: String = true,false
这向我们展示了编译器Bar[Foo]
通过递归应用宏来成功找到实例.
所以这种方法有效,但它也破坏了类型类提供的一些重大优势,例如基于运行时反射的解决方案来解决这类问题.当我们得到一些只是将它们从空中拉出来的宏时,可以更容易地推断出可用的实例.确定它能够找到什么的逻辑隐藏在宏实现代码中 - 它将在编译时运行,因此它在某种意义上仍然是类型安全的,但它不太透明.
这个实现也非常过度生成实例(尝试str(1)
),这很容易被纠正,但它很好地说明了这种东西有多危险.
对于它的价值,下面是一个使用的替代解决方案无形2.0的TypeClass
类型类,由Miles上述(你还可以看到我的博客文章在这里进行类似的比较).
implicit def BooleanTest = new TestTypeClass[Boolean] { def str(x: Boolean) = x.toString } def str[A: TestTypeClass](x: A) = implicitly[TestTypeClass[A]].str(x) import shapeless._ implicit object `TTC is a type class` extends ProductTypeClass[TestTypeClass] { def product[H, T <: HList](htc: TestTypeClass[H], ttc: TestTypeClass[T]) = new TestTypeClass[H :: T] { def str(x: H :: T) = { val hs = htc.str(x.head) val ts = ttc.str(x.tail) if (ts.isEmpty) hs else hs + "," + ts } } def emptyProduct = new TestTypeClass[HNil] { def str(x: HNil) = "" } def project[F, G](inst: => TestTypeClass[G], to: F => G, from: G => F) = new TestTypeClass[F] { def str(x: F) = inst.str(to(x)) } } object TestTypeClassHelper extends TypeClassCompanion[TestTypeClass] import TestTypeClassHelper.auto._
它并不是更简洁,但它更通用,不太可能做你不期望的事情.仍有神奇的事情发生,但它更容易控制和推理.