Как использовать ClassTag в макросах scala, реализованных для типа - PullRequest
0 голосов
/ 18 сентября 2018

Я написал макрос, который читает поля класса:

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 [String] Примечание: Nothing <: String, но набор свойств является инвариантным для типа A. Возможно, вы захотите исследовать подстановочный тип, такой как <code>_ <: String.(SLS 3.2.10) val arrayLikeFields: Set [String] = ArrayLikeFields.extract [T]

Я не могу этого понять.Как я могу решить мою проблему?

upd
Я прочитал scala 2.10.2, вызов "макро-метода" с универсальным типом не работает о материализации, ноу меня нет экземпляра класса

Ответы [ 3 ]

0 голосов
/ 08 октября 2018

Ответ на связанный вопрос, scala 2.10.2, вызывающий «метод макроса» с универсальным типом, не работает , также применяется здесь.

Вы пытаетесь решитьвременная проблема с макросом времени компиляции, которая невозможна.

Вызываемый метод toOrgJson[T] не может знать конкретный тип, который T представляет во время компиляции, а только получает эту информацию во время выполнения.Следовательно, вы не сможете выполнять какие-либо конкретные операции над T (например, перечислять его поля) во время компиляции, только во время выполнения.

Вы можете реализовать такую ​​операцию, как ArrayLikeFields.extract[T] ввремя выполнения с использованием Reflection, см., например, Получить список имен полей из класса дела

0 голосов
/ 10 октября 2018

Попробуйте подходить к материализации класса типа, как в 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())
  }
}
0 голосов
/ 05 октября 2018

Я не очень хорошо понимаю макросы, но кажется, что компилятор не понимает, что тип возвращаемой функции макроса равен Set[String].

.следующий трюк сработал для меня в scalaнужна верхняя граница, такая как T <: Person ... и это не то, что вы хотели ...

Оставьте здесь ответ, так как код компилируется, и это может помочь кому-то в направленииответ

...