web-dev-qa-db-fra.com

Comment utiliser Shapeless dans un Quasiquote?

J'essaie d'appeler une macro Shapeless de l'intérieur d'un quasiquote avec Scala et je ne comprends pas ce que j'aimerais obtenir.

Ma macro ne renvoie aucune erreur, mais ne développe pas Witness(fieldName) dans Witness.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 définition de Field:

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 à ceci:

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

Si cela avait été défini en dehors de quasiquote, cela générerait quelque chose comme:

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

Y a-t-il quelque chose qui peut être fait?

272
Roch

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}"
      }
    }
  }
}

On peut certes améliorer l'utilisation 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)

Ceci produit un objet compagnon MyCaseClass ayant requis Fields implique:

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 il a déjà été souligné, sans exemple de travail complet, il est assez difficile d’écrire une réponse exhaustive.

1
Federico Pellegatta