首页 > 解决方案 > 无形 HList 上的映射

问题描述

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

我有一个案例类和一个加密功能

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

我有同样的问题。

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

标签: scalagenericsshapeless

解决方案


我想出了另一种方法。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仅在实例中使用的字段stringEncrytorEncryptor用于HList检查Symbol's name头部是否在提供的HList字段中,如果是则应用,encypt否则跳过它。

用于LabelledGeneric使其适用于任何case class

提供相同的接口:

trait PayloadEncryptor[T] {
  def encrypt(source: T, encrypt: String => String): T
}

object PayloadEncryptor {

  def forClass[T](fieldNames: String*)(implicit encryptor: Encryptor[T]): PayloadEncryptor[T] = new PayloadEncryptor[T] {

    override def encrypt(source: T, encrypt: String => String): T = {
      encryptor.encryptFields(source, encrypt, fieldNames)
    }
  }
}

推荐阅读