Comment utiliser Shapeless dans un Quasiquote?

272

J'essaie d'appeler une Shapelessmacro de l'intérieur d'un quasiquoteavec Scalaet je n'obtiens pas ce que j'aimerais obtenir.

Ma macro ne renvoie aucune erreur mais ne se développe pas Witness(fieldName)dansWitness.Lt[String]

val implicits = schema.fields.map { field =>
  val fieldName:String = field.name
  val fieldType = TypeName(field.valueType.fullName)
  val in = TermName("implicitField"+fieldName)
  val tn = TermName(fieldName)
  val cc = TermName("cc")
  q"""implicit val $in = Field.apply[$className,$fieldType](Witness($fieldName), ($cc:   $className) => $cc.$tn)"""
}

Voici ma Fielddéfinition:

sealed abstract class Field[CC, FieldName] {
  val  fieldName: String
  type fieldType

  // How to extract this field
  def  get(cc : CC) : fieldType
}

object Field {
  // fieldType is existencial in Field but parametric in Fied.Aux
  // used to explict constraints on fieldType
  type Aux[CC, FieldName, fieldType_] = Field[CC, FieldName] {
    type fieldType = fieldType_
  }

  def apply[CC, fieldType_](fieldWitness : Witness.Lt[String], ext : CC => fieldType_) : Field.Aux[CC, fieldWitness.T, fieldType_] =
    new Field[CC, fieldWitness.T] {
      val fieldName  : String = fieldWitness.value
      type fieldType = fieldType_
      def get(cc : CC) : fieldType = ext(cc)
    }
}

Dans ce cas, l'implicite que je génère ressemble à:

implicit val implicitFieldname : Field[MyCaseClass, fieldWitness.`type`#T]{
  override type fieldType = java.lang.String
}

S'il avait été défini en dehors d'un, quasiquoteil générerait quelque chose comme:

implicit val implicitFieldname : Field.Aux[MyCaseClass, Witness.Lt[String]#T, String] = ...

Peut-on faire quelque chose?

Roch
la source
L'utilisez-vous dans une annotation de macro? Avez-vous essayé de fournir une annotation de type $in(que je pense qu'il faudra utiliser ConstantType)?
Travis Brown
@TravisBrown oui je le construis en utilisant une macro annotation (Macro Paradise). J'ai essayé de fournir un type comme celui-ci:q"""implicit val $in : Field.Aux[$className, Witness.Lt[String]#T, String] = Field.apply[$className,$fieldType](Witness($fieldName), ($cc: $className) => $cc.$tn)"""
Roch
Vous aurez cependant besoin du nom de champ spécifique dans l'annotation de type (voir par exemple mon ancien article de blog pré-Shapeless 2.0 ici pour un exemple d'utilisation ConstantType). Avez-vous un exemple de travail complet autour?
Travis Brown

Réponses:

1

Ceci est ma solution de travail en utilisant des annotations de macro à l'ancienne.

import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context
import scala.annotation.StaticAnnotation

class fieldable extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro fieldableMacro.impl
}

object fieldableMacro {
  def impl(c: Context)(annottees: c.Expr[Any]*): c.Tree = {
    import c.universe._
    annottees.map(_.tree) match {
      case (param @ q"case class $className(..$fields)") :: Nil => {
        val implicits = fields.collect {
          case field @ q"$mods val $tname: $tpt" => q"""
            implicit val $tname = Field.apply[$className,$tpt](
              Witness(${tname.decodedName.toString}), _.$tname
            )"""
        }; q"$param; object ${className.toTermName} {..$implicits}"
      }
    }
  }
}

Cela peut être, à coup sûr, amélioré en utilisant de meilleures quasiquotes, mais mon objectif était de montrer quelque chose d'aussi propre que possible.

Il peut être utilisé comme:

@fieldable
case class MyCaseClass(foo: String, bar: Int)

Cela produit un MyCaseClassobjet compagnon ayant des Fieldsimplicits requis :

implicit val foo = Field.apply[MyCaseClass, String](Witness("foo"), ((x$1) => x$1.foo));
implicit val bar = Field.apply[MyCaseClass, Int](Witness("bar"), ((x$2) => x$2.bar));

Comme cela a déjà été souligné, sans un exemple de travail complet, il est assez difficile d'écrire une réponse exhaustive.

Federico Pellegatta
la source