Скажем, у меня есть следующие два case class
es:
case class Address(street: String, city: String, state: String, zipCode: Int)
case class Person(firstName: String, lastName: String, address: Address)
и следующий экземпляр Person
класса:
val raj = Person("Raj", "Shekhar", Address("M Gandhi Marg",
"Mumbai",
"Maharashtra",
411342))
Теперь , если я хочу , чтобы обновление zipCode
в raj
то я должен сделать:
val updatedRaj = raj.copy(address = raj.address.copy(zipCode = raj.address.zipCode + 1))
С большим количеством уровней вложенности это становится еще более уродливым. Есть ли более чистый способ (что-то вроде Clojure update-in
) для обновления таких вложенных структур?
scala
case-class
zipper
missingfaktor
источник
источник
Ответы:
Молнии
Программа Huet's Zipper обеспечивает удобный обход и «мутацию» неизменной структуры данных. Scalaz предоставляет молнии для
Stream
( scalaz.Zipper ) иTree
( scalaz.TreeLoc ). Оказывается, структура застежки-молнии автоматически выводится из исходной структуры данных способом, который напоминает символическое дифференцирование алгебраического выражения.Но как это поможет вам с классами кейсов Scala? Что ж, Лукас Ритц недавно создал прототип расширения для scalac, которое будет автоматически создавать молнии для аннотированных классов case. Я воспроизведу его пример здесь:
Таким образом, сообществу необходимо убедить команду Scala в том, что эти усилия следует продолжить и интегрировать в компилятор.
Между прочим, Лукас недавно опубликовал версию Pacman, программируемую пользователем через DSL. Однако не похоже, что он использовал модифицированный компилятор, поскольку я не вижу никаких
@zip
аннотаций.Переписывание дерева
В других обстоятельствах вы можете применить какое-то преобразование ко всей структуре данных в соответствии с какой-либо стратегией (сверху вниз, снизу вверх) и на основе правил, которые соответствуют значению в некоторой точке структуры. Классический пример - преобразование AST для языка, возможно, для оценки, упрощения или сбора информации. Kiama поддерживает перезапись , посмотрите примеры в RewriterTests и посмотрите это видео . Вот отрывок, чтобы подогреть аппетит:
Обратите внимание, что для этого Kiama выходит за рамки системы типов.
источник
Забавно, что никто не добавил линзы, поскольку они были СДЕЛАНЫ для таких вещей. Итак, вот справочная статья CS по этому поводу , вот блог, в котором кратко говорится об использовании линз в Scala, вот реализация линз для Scalaz, и вот некоторый код, использующий ее, что удивительно похоже на ваш вопрос. И, чтобы сократить количество шаблонов , вот плагин, который генерирует линзы Scalaz для классов корпусов.
Что касается бонусных очков, вот еще один вопрос SO, который касается линз, и статья Тони Морриса.
Особенность линз в том, что они являются составными. Так что сначала они немного громоздки, но чем больше вы их используете, тем шире их популярность. Кроме того, они отлично подходят для тестирования, поскольку вам нужно тестировать только отдельные линзы, и вы можете принять их состав как должное.
Итак, исходя из реализации, представленной в конце этого ответа, вот как вы бы сделали это с линзами. Сначала объявите линзы, чтобы изменить почтовый индекс в адресе и адрес в человеке:
Теперь составьте их, чтобы получить линзу, меняющую почтовый индекс у человека:
Наконец, используйте эту линзу, чтобы изменить raj:
Или, используя синтаксический сахар:
Или даже:
Вот простая реализация, взятая из Scalaz, использованная для этого примера:
источник
personZipCodeLens.set(raj, personZipCodeLens.get(raj) + 1)
такой же, какpersonZipCodeLens mod (raj, _ + 1)
mod
не является примитивом для линз.Полезные инструменты для использования линз:
Сразу хочу добавить, что проекты Macrocosm и Rillit , основанные на макросах Scala 2.10, обеспечивают динамическое создание линз.
Использование Rillit:
Использование Macrocosm:
источник
Я искал, какая библиотека Scala имеет лучший синтаксис и лучшую функциональность, и одна библиотека, не упомянутая здесь, - это монокль, который для меня был действительно хорош. Пример ниже:
Это очень красиво, и есть много способов комбинировать линзы. Scalaz, например, требует большого количества шаблонов, он быстро компилируется и отлично работает.
Чтобы использовать их в своем проекте, просто добавьте это в свои зависимости:
источник
Shapeless делает свое дело:
с участием:
Обратите внимание, что хотя некоторые другие ответы здесь позволяют вам составлять линзы, чтобы глубже проникнуть в заданную структуру, эти бесформенные линзы (и другие библиотеки / макросы) позволяют комбинировать две несвязанные линзы, так что вы можете сделать линзу, которая устанавливает произвольное количество параметров в произвольные положения в вашей структуре. Для сложных структур данных очень полезна дополнительная композиция.
источник
Lens
код из ответа Дэниела С. Собрала и поэтому избегал добавления внешней зависимости.Благодаря своей составной природе линзы являются прекрасным решением проблемы сильно вложенных структур. Однако при низком уровне вложенности я иногда чувствую, что линз слишком много, и я не хочу вводить подход с линзами целиком, если есть только несколько мест с вложенными обновлениями. Для полноты картины вот очень простое / прагматичное решение для этого случая:
Я просто написал несколько
modify...
вспомогательных функций в структуре верхнего уровня, которые имеют дело с уродливой вложенной копией. Например:Моя основная цель (упрощение обновления на стороне клиента) достигнута:
Очевидно, что создание полного набора помощников по модификации утомляет. Но для внутренних вещей часто можно просто создать их при первой попытке изменить определенное вложенное поле.
источник
Возможно, QuickLens лучше соответствует вашему вопросу. QuickLens использует макросы для преобразования дружественного IDE выражения в нечто близкое к исходному оператору копирования.
Учитывая два примера классов case:
и экземпляр класса Person:
вы можете обновить zipCode raj с помощью:
источник