I am trying to generate a typeclass for returning the value of a case class field that has a particular annotation at compile time, using shapeless.
That's given a Scala annotation case class and a generic case class the typeclass Identity[T]
should return the value of the 'single' attribute annotated with such an annotation.
trait Identity[T] {
def apply(t: T): Long
}
final case class Id() extends scala.annotation.StaticAnnotation
case class User(@Id id: Long, account_id: Int, name: String, updated_at: java.time.Instant)
and given an instance of the case class and typeclass
val identity = Identity[User] // implicit summoner
val user = User(1009, 101, "Alessandro", Instant.now())
val value = identity(user)
I'd like value
to return 1009
I tried to play around the following snippet, but I did get as far as to compute the Symbol
name of the annotated field.
object WithShapeless {
import MyAnnotations.Id
import shapeless._
import shapeless.ops.hlist
import shapeless.ops.record.Keys
import shapeless.record._
// select the field symbol with the desired annotation, if exists
object Mapper extends Poly1 {
implicit def some[K <: Symbol]: Case.Aux[(K, Some[Id]), Option[K]] = at[(K, Some[Id])] {
case (k, _) => Some(k)
}
implicit def none[K <: Symbol]: Case.Aux[(K, None.type), Option[K]] = at[(K, None.type)] {
case (k, _) => Option.empty[K]
}
}
implicit def gen[A, HL <: HList, AL <: HList, KL <: HList, ZL <: HList, ML <: HList](
implicit
generic: LabelledGeneric.Aux[A, HL],
annots: Annotations.Aux[Id, A, AL],
keys: Keys.Aux[HL, KL],
zip: hlist.Zip.Aux[KL :: AL :: HNil, ZL],
mapper: hlist.Mapper.Aux[Mapper.type, ZL, ML],
ev0: hlist.ToList[ML, Option[Symbol]]
): Identity[A] = new Identity[A] {
val zipped: ZL = zip(keys() :: annots() :: HNil)
val annotatedFields: ML = mapper.apply(zipped)
val symbol: Symbol = annotatedFields.to[List].find(_.isDefined).get match {
case Some(symbol) => symbol
case _ => throw new Exception(s"Class has no attribute marked with @IdAnnot")
}
println(s"""
|zipped: ${zipped}
|mapped: ${annotatedFields}
|symbol: $symbol
|""".stripMargin)
override def apply(a: A): Long = {
val repr = generic.to(a)
val value = repr.get(Witness(symbol)) // compilation fails here
println(s"""
|Generic ${generic.to(a)}
|value: $value
""".stripMargin)
1
}
}
}
I try to conjure a Selector
to return the value but the compiler fails with No field this.symbol.type in record HL
.
I cannot get it to work! Thanks