无形HList上的映射

时间:2018-06-04 14:11:44

标签: scala generics shapeless

我想创建以下代码的通用版本:

我有一个案例类和一个加密函数

case class Cat(name: String, age: Int, color: String)
val encrypt : String => String = _.hashCode.toString // as an example
val encryptableFields = Seq("color")

我有Poly1,它将在我的HList

中进行映射
import shapeless._
import labelled._
import record._

trait enc extends Poly1 {
  implicit def defaultEncrypt[K,V] = at[(K, V)] { case (k,v) =>field[K](v)}
}
object pol extends enc {
  implicit def stringEncrypt[K <: Symbol] = at[(K, String)] { case (k,v) => field[K](if(encryptableFields contains k.name) encrypt(v) else v)}
}

当我使用它时,它按预期工作:

val cat = Cat("name", 1, "black")
val lgCat = LabelledGeneric[Cat]
val la = lgCat.to(cat)
val a = la.fields.map(pol)
lgCat.from(a)
// Cat("name", 1, "93818879")

因为它有效,我正在考虑以通用方式创建它并封装功能和类型类,如:

trait Encryptor[T] {
    val fields: Seq[String]   
    def encryptFields(source: T, encrypt: String => String): T
}

object Encryptor {

    def forClass[A <: Product](f: Seq[String]) = new Encryptor[A] {
        val fields: Seq[String] = f

        override def encryptFields(source:A, encrypt: String => String): A = {

            object pol extends enc {
                implicit def stringEncrypt[K <: Symbol] = at[(K, String)] { case (k, v) => field[K](if (f contains k.name) encrypt(v) else v) }
            }

            val gen = LabelledGeneric[A]
            val hList = gen.to(source)
            val updated = hList.fields.map(pol)
            gen.from(updated)
        }
    }

}

通过这个实现,我得到以下编译时错误:

Error:could not find implicit value for parameter lgen: shapeless.LabelledGeneric[A]
  val gen = LabelledGeneric[A]

尝试通过传递LabelledGeneric[A]隐含地提出更多问题来解决它。

 def forClass[A <: Product, R <: HList](f: Seq[String])(implicit gen: implicit gen: LabelledGeneric.Aux[A, R]) = new Encryptor[A] { ... }

抱怨Error:(46, 27) could not find implicit value for parameter fields: shapeless.ops.record.Fields[gen.Repr]; val updated = hList.fields.map(pol)

试图传递一个时:

def forClass[A <: Product, R <: HList, FOut <: HList](f: Seq[String])(
   implicit gen: LabelledGeneric.Aux[A, R], fields: Fields.Aux[R, FOut])

我有同样的问题。

我想知道如何克服这个问题。

1 个答案:

答案 0 :(得分:3)

我提出了另一种方法。 您可以将其分解为较小的部分,并使用不同的方法在HList上进行操作,而不是一次性完成所有操作。

让我们为内部表示创建一个类型类:

trait Encryptor[T] {

  def encryptFields(source: T, encrypt: String => String, fields: Seq[String]): T
}

在您的示例中,您只有IntString字段,所以我会坚持这一点。

import shapeless._
import labelled._

object Encryptor {

  def apply[A](implicit enc: Encryptor[A]): Encryptor[A] = enc

  implicit val stringEncryptor: Encryptor[String] = new Encryptor[String] {
    override def encryptFields(source: String, encrypt: String => String, fields: Seq[String]) = encrypt(source)
  }

  implicit val intEncryptor: Encryptor[Int] = new Encryptor[Int] {
    override def encryptFields(source: Int, encrypt: String => String, fields: Seq[String]) = source
  }

  implicit val hnilEncryptor: Encryptor[HNil] = new Encryptor[HNil] {
    override def encryptFields(source: HNil, encrypt: String => String, fields: Seq[String]) = HNil
  }

  implicit def hlistEncryptor[A, K <: Symbol, H, T <: HList](
    implicit
    witness: Witness.Aux[K],
    hEncryptor: Lazy[Encryptor[H]],
    tEncryptor: Encryptor[T]
  ): Encryptor[FieldType[K, H] :: T] = new Encryptor[FieldType[K, H] :: T] {

    val fieldName: String = witness.value.name

    override def encryptFields(source: FieldType[K, H] :: T, encrypt: String => String, fields: Seq[String]) = {
      val tail = tEncryptor.encryptFields(source.tail, encrypt, fields)
      val head = if (fields contains fieldName) field[K](hEncryptor.value.encryptFields(source.head, encrypt, fields))
      else source.head
      head :: tail
    }
  }

  import shapeless.LabelledGeneric

  implicit def genericObjectEncryptor[A, H <: HList](
    implicit
    generic: LabelledGeneric.Aux[A, H],
    hEncryptor: Lazy[Encryptor[H]]
  ): Encryptor[A] = new Encryptor[A] {

    override def encryptFields(source: A, encrypt: String => String, fields: Seq[String]) = {
      generic.from(hEncryptor.value.encryptFields(generic.to(source), encrypt, fields))
    }
  }
}

因为在您的示例中,您仅在encrypt字段上应用String函数,因此它仅在stringEncrytor实例中使用。 Encryptor的{​​{1}}检查HList头部的Symbol's name是否在提供的字段中,如果是,则应用HList否则会跳过它。

使用encypt使其适用于任何LabelledGeneric

提供相同的界面:

case class