问题描述
我正在尝试生成一个类型类,用于返回在编译时具有特定注释的 case 类字段的值,使用 shapeless。
给定了一个 Scala 注释案例类和一个泛型案例类 typeclass Identity[T]
应返回使用此类注释注释的“单个”属性的值。
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)
并给出案例类和类型类的实例
val identity = Identity[User] // implicit summoner
val user = User(1009,101,"Alessandro",Instant.Now())
val value = identity(user)
我希望 value
返回 1009
我尝试使用以下代码段,但我确实计算出了带注释字段的 Symbol
名称。
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),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
}
}
}
我尝试使用 Selector
来返回值,但编译器因 No field this.symbol.type in record HL
而失败。
我无法让它工作! 谢谢
解决方法
实际上您不需要 LabelledGeneric
,因为您不使用密钥。试试
import java.time.Instant
import shapeless.ops.hlist.{CollectFirst,Zip}
import shapeless.{::,Annotations,Generic,HList,HNil,Poly1}
trait Identity[T] {
type Out
def apply(t: T): Out
}
object Identity {
type Aux[T,Out0] = Identity[T] { type Out = Out0 }
def instance[T,Out0](f: T => Out0): Aux[T,Out0] = new Identity[T] {
type Out = Out0
override def apply(t: T): Out = f(t)
}
def apply[T](implicit identity: Identity[T]): Aux[T,identity.Out] = identity
implicit def mkIdentity[T,HL <: HList,AL <: HList,ZL <: HList](implicit
generic: Generic.Aux[T,HL],annotations: Annotations.Aux[Id,T,AL],zip: Zip.Aux[HL :: AL :: HNil,ZL],collectFirst: CollectFirst[ZL,Mapper.type],): Aux[T,collectFirst.Out] =
instance(t => collectFirst(zip(generic.to(t) :: annotations() :: HNil)))
}
object Mapper extends Poly1 {
implicit def cse[A]: Case.Aux[(A,Some[Id]),A] = at(_._1)
}
final case class Id() extends scala.annotation.StaticAnnotation
case class User(@Id id: Long,account_id: Int,name: String,updated_at: java.time.Instant)
val identity = Identity[User]
val user = User(1009,101,"Alessandro",Instant.now())
val value = identity(user) // 1009