我正在编写一个Scala隐式宏,它自动为case类生成一个类型类(使用quasiquote,Scala 2.10.3包含宏天堂编译器插件和Scala 2.11.0-M7)。
隐式宏以递归方式查找参数的类型类。
只要case类不接受类型参数或者在生成的代码中没有使用类型参数,它就可以正常工作。
但是一旦需要<TypeClass>[<TypeParameter of case class>]
的隐含值,调用站点的编译将失败,并且“找不到参数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]]
设计是这样,我做错了什么,还是编译错误?
答案 0 :(得分:11)
有一些表面级别的问题可能会阻止您的版本完全,但一旦它们被解决,您应该能够完全按照自己的意愿行事(无论是好主意还是不是另一个问题 - 我将在本答案结尾处尝试解决这个问题。)
这三个最大的问题是:
q"str($param)"
首先,在生成的代码的上下文中,str
将引用您正在定义和实例化的匿名类上的方法,而不是str
上Test
方法的引用。 1}}。接下来,这将生成看起来像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]
的实例。
所以这种方法有效,但它也破坏了类型类提供的一些重大优势,例如:基于运行时反射的解决方案来解决这类问题。当我们得到一些只是将它们从空中拉出来的宏时,可以更容易地推断出可用的实例。确定它能够找到什么的逻辑隐藏在宏实现代码中 - 它将在编译时运行,因此它在某种意义上仍然是类型安全的,但它不太透明。
这个实现也非常过度生成实例(try str(1)
),这很容易被纠正,但它很好地说明了这种东西有多危险。
对于它的价值,以下是使用上面Miles提到的Shapeless 2.0 TypeClass
类型类的替代解决方案(您还可以查看我的博文here比较)。
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._
它并不是更简洁,但它更通用,不太可能做你不期望的事情。仍有神奇的事情发生,但它更容易控制和推理。