无形和注释

Shapeless and annotations

我想将一些函数应用到用 MyAnnotation 注释的案例 class 中的字段。这个想法是将类型 T 转换为其通用表示,提取注释,压缩,向右(或向左)折叠以重建通用表示,最后返回类型 T。我按照提供的答案 and this gist.

我正在使用 scala 2.11.12 和 shapeless 2.3.3。

以下是我的代码:

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

上面的代码可以编译。但是,在测试中实例化 Modifier 时:

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

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

测试文件编译不通过,报如下错误:

  [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

问题似乎来自正确的文件夹定义:当从 genericModifier 中的隐式列表中删除 rightFolder 时,它起作用了:

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

怎么了?

你的代码有几个错误:

  • 只为 Option 定义 Poly 过于粗略(模式匹配在运行时执行,编译器应该知道 SomeNone 的定义在编译时)

  • HNil 应该代替 HNil.typeHNil : HNil 而不是 HNil(类型 HNilHNil.type不同)

  • 编译器不知道 RightFolder 实际上 returns 原来的 HList 类型,所以你应该使用 RightFolder.Aux 类型。

正确的代码是

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