Shapeless and annotations

99封情书 提交于 2021-01-29 07:23:18

问题


I would like to have some function applied to fields in a case class, that are annotated with MyAnnotation. The idea is to transform type T into its generic representation, extract annotations, zip, fold right (or left) to reconstruct a generic representation and finally get back to type T. I followed the answer provided here and this gist.

I'm using scala 2.11.12 and shapeless 2.3.3.

Hereafter is my code:

import shapeless._
import shapeless.ops.hlist._

case class MyAnnotation(func: String) extends scala.annotation.StaticAnnotation

trait Modifier[T] {
  def modify(t: T): T
}

object Modifier {

  def apply[A: Modifier]: Modifier[A] = implicitly[Modifier[A]]

  def create[T](func: T => T): Modifier[T] = new Modifier[T] { override def modify(t: T): T = func(t) }

  private def id[T](t: T) = t

  implicit val stringModifier: Modifier[String] = create(id)

  implicit val booleanModifier: Modifier[Boolean] = create(id)

  implicit val byteModifier: Modifier[Byte] = create(id)

  implicit val charModifier: Modifier[Char] = create(id)

  implicit val doubleModifier: Modifier[Double] = create(id)

  implicit val floatModifier: Modifier[Float] = create(id)

  implicit val intModifier: Modifier[Int] = create(id)

  implicit val longModifier: Modifier[Long] = create(id)

  implicit val shortModifier: Modifier[Short] = create(id)

  implicit val hnilModifier: Modifier[HNil] = create(id)

  implicit def hlistModifier[H, T <: HList, AL <: HList](
    implicit
    hser: Lazy[Modifier[H]],
    tser: Modifier[T]
  ): Modifier[H :: T] = new Modifier[H :: T] {
    override def modify(ht: H :: T): H :: T = {
      ht match {
        case h :: t =>
          hser.value.modify(h) :: tser.modify(t)
      }
    }
  }

  implicit val cnilModifier: Modifier[CNil] = create(id)

  implicit def coproductModifier[L, R <: Coproduct](
    implicit
    lser: Lazy[Modifier[L]],
    rser: Modifier[R]
  ): Modifier[L :+: R] = new Modifier[L :+: R] {
    override def modify(t: L :+: R): L :+: R = t match {
      case Inl(l) => Inl(lser.value.modify(l))
      case Inr(r) => Inr(rser.modify(r))
    }
  }

  object Collector extends Poly2 {
    implicit def myCase[ACC <: HList, E] = at[(E, Option[MyAnnotation]), ACC] {
      case ((e, None), acc) => e :: acc
      case ((e, Some(MyAnnotation(func))), acc) => {
        println(func)
        e :: acc
      }
    }
  }

  implicit def genericModifier[T, HL <: HList, AL <: HList, ZL <: HList](
    implicit
    gen: Generic.Aux[T, HL],
    ser: Lazy[Modifier[HL]],
    annots: Annotations.Aux[MyAnnotation, T, AL],
    zip: Zip.Aux[HL :: AL :: HNil, ZL],
    rightFolder: RightFolder[ZL, HNil.type, Collector.type]
  ): Modifier[T] = new Modifier[T] {
    override def modify(t: T): T = {
      val generic = gen.to(t)
      println(generic)
      val annotations = annots()
      println(annotations)
      val zipped = zip(generic :: annotations :: HNil)
      println(zipped)
      val modified = zipped.foldRight(HNil)(Collector)
      println(modified)

      val typed = gen.from(generic) // temporary
      typed
    }
  }
}

The code above compiles. However, when instanciating a Modifier in a test:

  case class Test(a: String, @MyAnnotation("sha1") b: String)

  val test = Test("A", "B")
  val modifier: Modifier[Test] = implicitly

the test file does not compile and give the following error:

  [error] ambiguous implicit values:
  [error]  both value StringCanBuildFrom in object Predef of type => 
           scala.collection.generic.CanBuildFrom[String,Char,String]
  [error]  and method $conforms in object Predef of type [A]=> <:<[A,A]
  [error]  match expected type T
  [error]       val ser1: Modifier[Test] = implicitly

The problem seems to come from the right folder definition: when removing rightFolder from the list of implicits in genericModifier, then it works:

  implicit def genericModifier[T, HL <: HList, AL <: HList, ZL <: HList](
    implicit
    gen: Generic.Aux[T, HL],
    ser: Lazy[Modifier[HL]],
    annots: Annotations.Aux[MyAnnotation, T, AL],
    zip: Zip.Aux[HL :: AL :: HNil, ZL]/*,
    rightFolder: RightFolder[ZL, HNil.type, Collector.type]*/
  ): Modifier[T] = new Modifier[T] {
    override def modify(t: T): T = {
      val generic = gen.to(t)
      println(generic)
      val annotations = annots()
      println(annotations)
      val zipped = zip(generic :: annotations :: HNil)
      println(zipped)
      /*val modified = zipped.foldRight(HNil)(Collector)
      println(modified)*/

      val typed = gen.from(generic) // temporary
      typed
    }
  }

What is wrong?


回答1:


There are several mistakes in your code:

  • defining Poly just for Option is too rough (pattern matching is performed at runtime and compiler should know definitions for Some and None at compile time)

  • HNil should be instead of HNil.type and HNil : HNil instead of HNil (types HNil and HNil.type are different)

  • compiler doesn't know that RightFolder actually returns the original HList type, so you should use RightFolder.Aux type.

Correct code is

import shapeless.ops.hlist.{RightFolder, Zip}
import shapeless.{::, Annotations, Generic, HList, HNil, Lazy, Poly2}
import scala.annotation.StaticAnnotation

object App {
  case class MyAnnotation(func: String) extends StaticAnnotation

  object Collector extends Poly2 {
//    implicit def myCase[ACC <: HList, E] = at[(E, Option[PII]), ACC] {
//      case ((e, None), acc) => e :: acc
//      case ((e, Some(MyAnnotation(func))), acc) => {
//        println(func)
//        e :: acc
//      }
//    }

    implicit def someCase[ACC <: HList, E]: Case.Aux[(E, Some[MyAnnotation]), ACC, E :: ACC] = at {
      case ((e, Some(MyAnnotation(func))), acc) =>
        println(func)
        e :: acc
    }

    implicit def noneCase[ACC <: HList, E]: Case.Aux[(E, None.type), ACC, E :: ACC] = at {
      case ((e, None), acc) => e :: acc
    }
  }

  trait Modifier[T] {
    def modify(t: T): T
  }

  implicit def hListModifier[HL <: HList]: Modifier[HL] = identity(_) 
  // added as an example, you should replace this with your Modifier for HList

  implicit def genericModifier[T, HL <: HList, AL <: HList, ZL <: HList](implicit
    gen: Generic.Aux[T, HL],
    ser: Lazy[Modifier[HL]],
    annots: Annotations.Aux[MyAnnotation, T, AL],
    zip: Zip.Aux[HL :: AL :: HNil, ZL],
    rightFolder: RightFolder.Aux[ZL, HNil/*.type*/, Collector.type, HL /*added*/]
    ): Modifier[T] = new Modifier[T] {
    override def modify(t: T): T = {
      val generic = gen.to(t)
      println(generic)
      val annotations = annots()
      println(annotations)
      val zipped = zip(generic :: annotations :: HNil)
      println(zipped)
      val modified = zipped.foldRight(HNil : HNil /*added*/)(Collector)
      println(modified)

      val typed = gen.from(modified)
      typed
    }
  }

  case class Test(a: String, @MyAnnotation("sha1") b: String)

  val test = Test("A", "B")
  val modifier: Modifier[Test] = implicitly[Modifier[Test]]

  def main(args: Array[String]): Unit = {
    val test1 = modifier.modify(test) // prints "sha1"
    println(test1) // Test(A,B)
  }
}


来源:https://stackoverflow.com/questions/54412106/shapeless-and-annotations

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!