Как напечатать 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
Притворимся у них. Для более сложных объектов состав линзы работает как обычно: внутренняя линза просто воздействует на извлеченный объект, полагаясь на внешний, чтобы упаковать его обратно.