Shapeless:提取由注释参数化的 case case 字段值

问题描述

我正在尝试生成一个类型类,用于返回在编译时具有特定注释的 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