我写了一个宏,它读取类字段:
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
object ArrayLikeFields {
def extract[T]: Set[String] = macro extractImpl[T]
def extractImpl[T: c.WeakTypeTag](c: whitebox.Context): c.Expr[Set[String]] = {
import c.universe._
val tree = weakTypeOf[T].decls
.collectFirst {
case m: MethodSymbol if m.isPrimaryConstructor => m
}
.map(y => y.paramLists.headOption.getOrElse(Seq.empty))
.getOrElse(Seq.empty)
.map(s => q"${s.name.decodedName.toString}")
c.Expr[Set[String]] {
q"""Set(..$tree)"""
}
}
}
我能够针对具体类型进行编译和运行:
object Main extends App {
case class Person(name:String)
val res: Set[String] = ArrayLikeFields.extract[Person]
}
但是我想将它用于这样的通用类型:
object Lib {
implicit class SomeImplicit(s: String) {
def toOrgJson[T]: JSONObject = {
val arrayLikeFields: Set[String] = ArrayLikeFields.extract[T]
//some code, that uses fields, etc
null
}
}
}
编译错误:
错误:(14,65)类型不匹配;发现: scala.collection.immutable.Set [不需要]:Set [字符串]注意: 什么都不是:字符串,但是特征集在类型A中是不变的。您可能希望 调查诸如
_ <: String
之类的通配符类型。 (SLS 3.2.10) val arrayLikeFields:Set [String] = ArrayLikeFields.extract [T]
我不能理解这一点。我该如何解决我的问题?
更新
我读过关于实现的scala 2.10.2 calling a 'macro method' with generic type not work,但我没有课程实例
答案 0 :(得分:3)
尝试实现像1中的类型类的方法
object Main extends App {
case class Person(name:String)
val res: Set[String] = ArrayLikeFields.extract[Person] //Set(name)
import Lib._
"abc".toOrgJson[Person] // prints Set(name)
}
object Lib {
implicit class SomeImplicit(s: String) {
def toOrgJson[T: ArrayLikeFields.Extract]: JSONObject = {
val arrayLikeFields: Set[String] = ArrayLikeFields.extract[T]
//some code, that uses fields, etc
println(arrayLikeFields) //added
null
}
}
}
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
object ArrayLikeFields {
def extract[T](implicit extr: Extract[T]): Set[String] = extr()
trait Extract[T] {
def apply(): Set[String]
}
object Extract {
implicit def materializeExtract[T]: Extract[T] = macro materializeExtractImpl[T]
def materializeExtractImpl[T: c.WeakTypeTag](c: whitebox.Context): c.Expr[Extract[T]] = {
import c.universe._
val tree = weakTypeOf[T].decls
.collectFirst {
case m: MethodSymbol if m.isPrimaryConstructor => m
}
.map(y => y.paramLists.headOption.getOrElse(Seq.empty))
.getOrElse(Seq.empty)
.map(s => q"${s.name.decodedName.toString}")
c.Expr[Extract[T]] {
q"""new ArrayLikeFields.Extract[${weakTypeOf[T]}] {
override def apply(): _root_.scala.collection.immutable.Set[_root_.java.lang.String] =
_root_.scala.collection.immutable.Set(..$tree)
}"""
}
}
}
}
实际上,我认为您不需要白盒宏,黑盒宏就足够了。因此,您可以将(c: whitebox.Context)
替换为(c: blackbox.Context)
。
顺便说一句,可以通过Shapeless而不是宏来解决相同的问题(宏可以在Shapeless的背景下工作)
object Main extends App {
case class Person(name:String)
val res: Set[String] = ArrayLikeFields.extract[Person] //Set(name)
}
object ArrayLikeFields {
def extract[T: Extract]: Set[String] = implicitly[Extract[T]].apply()
trait Extract[T] {
def apply(): Set[String]
}
object Extract {
def instance[T](strs: Set[String]): Extract[T] = () => strs
implicit def genericExtract[T, Repr <: HList](implicit
labelledGeneric: LabelledGeneric.Aux[T, Repr],
extract: Extract[Repr]
): Extract[T] = instance(extract())
implicit def hconsExtract[K <: Symbol, V, T <: HList](implicit
extract: Extract[T],
witness: Witness.Aux[K]
): Extract[FieldType[K, V] :: T] =
instance(extract() + witness.value.name)
implicit val hnilExtract: Extract[HNil] = instance(Set())
}
}
答案 1 :(得分:2)
链接的问题scala 2.10.2 calling a 'macro method' with generic type not work的答案也适用于此。
您正在尝试使用编译时宏解决运行时问题,这是不可能的。
被调用的方法toOrgJson[T]
无法知道T
在编译时表示的具体类型,而只能在运行时获取该信息。因此,仅在运行时,您将无法在T
上对ArrayLikeFields.extract[T]
进行任何具体操作(例如列出其字段)。
您可以在运行时使用Reflection实现类似onClick
之类的操作,请参见例如Get field names list from case class
答案 2 :(得分:0)
我对宏没有非常扎实的理解,但是似乎编译器无法理解宏函数的返回类型是 Set[String]
。
以下技巧在scala 2.12.7中对我有效
def toOrgJson[T]: JSONObject = {
val arrayLikeFields: Set[String] = ArrayLikeFields.extract[T].map(identity[String])
//some code, that uses fields, etc
null
}
编辑
实际上要获取一个非空的Set
T
需要一个上限,例如T <: Person
...而那不是您想要的...
因为代码可以编译,所以在这里留下答案,这可能会帮助某个人找到答案