Как напечатать Monocle Lens в виде строки стиля метода доступа к свойству

Используя Monocle, я могу определить Lens для чтения элемента класса case без проблем,

    val md5Lens = GenLens[Message](_.md5)

Это может использоваться для сравнения стоимости md5 между двумя объектами и завершается ошибкой с сообщением об ошибке, которое включает имя поля, если значения различаются.

Есть ли способ создать удобную строку из Lens один, который идентифицирует поле, читаемое объективом? Я хочу избежать явного указания имени поля

    val md5LensAndName = (GenLens[Message](_.md5), "md5")

Если есть решение, которое также работает с линзами с более чем одним компонентом, то даже лучше. Для меня было бы хорошо, даже если бы решение работало только до глубины одного.

1 ответ

Решение

Это принципиально невозможно. Концептуально линза - это не что иное, как пара функций: одна для получения значения от объекта и одна для получения нового объекта с использованием заданного значения. Эти функции могут быть реализованы посредством доступа к полям исходного объекта или нет. На самом деле, даже GenLens В макросе можно использовать цепочки полей доступа, такие как _.field1.field2 генерировать составные линзы для полей вложенных объектов. Поначалу это может сбивать с толку, но эта функция имеет свое применение. Например, вы можете отделить формат хранения и представления данных:

import monocle._

case class Person private(value: String) {

  import Person._

  private def replace(
    array: Array[String], index: Int, item: String
  ): Array[String] = {
    val copy = Array.ofDim[String](array.length)
    array.copyToArray(copy)
    copy(index) = item
    copy
  }

  def replaceItem(index: Int, item: String): Person = {
    val array = value.split(delimiter)
    val newArray = replace(array, index, item)
    val newValue = newArray.mkString(delimiter)
    Person(newValue)
  }

  def getItem(index: Int): String = {
    val array = value.split(delimiter)
    array(index)
  }
}

object Person {

  private val delimiter: String = ";"

  val nameIndex: Int = 0

  val cityIndex: Int = 1

  def apply(name: String, address: String): Person =
    Person(Array(name, address).mkString(delimiter))
}

val name: Lens[Person, String] =
  Lens[Person, String](
    _.getItem(Person.nameIndex)
  )(
    name => person => person.replaceItem(Person.nameIndex, name)
  )

val city: Lens[Person, String] =
  Lens[Person, String](
    _.getItem(Person.cityIndex)
  )(
    city => person => person.replaceItem(Person.cityIndex, city)
  )

val person = Person("John", "London")
val personAfterMove = city.set("New York")(person)
println(name.get(personAfterMove)) // John
println(city.get(personAfterMove)) // New York

Хотя этот пример не очень эффективен, он иллюстрирует идею: Person класс не имеет city или же address поля, но оборачивая экстрактор данных и функцию перестроения строк в LensПритворимся у них. Для более сложных объектов состав линзы работает как обычно: внутренняя линза просто воздействует на извлеченный объект, полагаясь на внешний, чтобы упаковать его обратно.

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