代码之家  ›  专栏  ›  技术社区  ›  zella

如何在为类型实现的scala宏中使用ClassTag

  •  3
  • zella  · 技术社区  · 7 年前

    我写了一个宏,它读取类字段:

    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[Nothing]必需:Set[String]注意: Nothing<:字符串,但trait Set在类型A中是不变的。您可能希望 调查通配符类型,例如 _ <: String val arrayLikeFields:设置[String]=ArrayLikeFields.extract文件[T]

    升级版
    scala 2.10.2 calling a 'macro method' with generic type not work

    3 回复  |  直到 7 年前
        1
  •  3
  •   Dmytro Mitin    7 年前

    尝试具体化类型类的方法,如 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) .

    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())
      }
    }
    
        2
  •  2
  •   Rich    7 年前

    关于相关问题的答案, scala 2.10.2 calling a 'macro method' with generic type not work ,也适用于此处。

    toOrgJson[T] 无法知道 T T型

    您可以实现如下操作 ArrayLikeFields.extract[T] 在运行时使用反射,请参见。 Get field names list from case class

        3
  •  0
  •   Yaneeve    7 年前

    我对宏的理解不是很透彻,但是编译器似乎不理解宏函数的返回类型 Set[String] .

    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 ... 那不是你想要的。。。

    把答案留在这里,因为代码是编译的,它可能会帮助人们找到答案

    推荐文章