Отображение на бесформенном 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])

У меня такая же проблема.

Интересно, как решить эту проблему.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
272
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я придумал другой подход. Вместо того, чтобы делать все сразу, вы можете разбить его на более мелкие части и использовать 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)
    }
  }
}

Другие вопросы по теме