Картирование бесформенного HList - PullRequest
0 голосов
/ 04 июня 2018

Я хотел бы создать общую версию следующего кода:

У меня есть класс дел и функция шифрования

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

У меня есть Poly1, который будет выполнять сопоставление вmy 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 голосов
/ 06 июня 2018

Я придумал другой подход.Вместо того, чтобы делать все сразу, вы можете разбить его на более мелкие части и работать на HList с другим подходом.

Давайте создадим класс типов для внутреннего представления:

trait Encryptor[T] {

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

В вашем примере у вас есть только поля Int и String, поэтому я буду придерживаться этого.

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 для 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)
    }
  }
}
...