Como usar Shapeless em um Quasiquote?

272

Estou tentando chamar uma Shapelessmacro de dentro de um quasiquotecom Scalae não estou conseguindo o que gostaria de obter.

Minha macro não retorna nenhum erro, mas não se expande Witness(fieldName)paraWitness.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)"""
}

Aqui está a minha Fielddefinição:

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

Nesse caso, o implícito que eu gero se parece com:

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

Se tivesse sido definido fora de um quasiquote, geraria algo como:

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

Existe algo que pode ser feito?

Roch
fonte
Você está usando isso em uma anotação de macro? Você já tentou fornecer uma anotação de tipo $in(que acho que exigirá o uso ConstantType)?
Travis Brown
@TravisBrown sim Estou construindo isso usando uma anotação de macro (Macro Paradise). Eu tentei fornecer um tipo como este:q"""implicit val $in : Field.Aux[$className, Witness.Lt[String]#T, String] = Field.apply[$className,$fieldType](Witness($fieldName), ($cc: $className) => $cc.$tn)"""
Roch
Você precisará do nome do campo específico na anotação de tipo (veja, por exemplo, minha antiga postagem no blog anterior ao Shapeless 2.0 aqui para um exemplo de uso ConstantType). Você tem um exemplo completo de trabalho?
Travis Brown

Respostas:

1

Esta é minha solução de trabalho usando anotações de macro em estilo antigo.

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

Com certeza, ele pode ser melhorado usando quasiquotas melhores, mas meu objetivo era mostrar algo o mais limpo possível.

Pode ser usado como:

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

Isso produz um MyCaseClassobjeto complementar com Fieldsimplícitos necessários :

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));

Como já foi apontado, sem um exemplo completo de trabalho, é bastante difícil escrever uma resposta exaustiva.

Federico Pellegatta
fonte