Модификация карты через монокль
Я хотел попробовать линзы, и библиотека Monocle казалась (с моей нубистской точки зрения) хорошей со всеми этими модными без шаблонов @Lenses
, К сожалению, я обнаружил, что для начинающих практически нет учебных материалов (я знаю основы FP в ванильной Scala, но не в Scalaz). В официальном учебнике отсутствуют простые примеры (и / или их результаты) и миксы в довольно сложной библиотеке Scalaz. Можно предположить, что такая простая задача, как доступ к карте, будет рассмотрена на первой странице.
У меня есть следующий фрагмент:
@Lenses case class House(presentsDelivered: Int)
type Houses = Map[(Int, Int), House]
@Lenses case class Town(houses: Houses)
@Lenses case class Santa(x: Int, y: Int)
@Lenses case class World(santa: Santa, town: Town)
Я видел at
а также index
, но нет простых примеров (просто какой-то странный [волшебный для меня] ответ с applyOptional
что требуется шаблон). Я хочу обновить карту - houses
в Town
, Я пытался что-то в этом духе:
(World.town ^|-> Town.houses ^|-> index((x, y)) ^|-> House.presentsDelivered)
.modify { _ + 1 }(world)
Что синтаксически неправильно, но я думаю, что очевидно, что я хотел сделать (изменить presentsDelivered
из House
по указанному x, y
координаты). Итак, мой вопрос, как изменить index
часть для доступа к карте?
Любые подсказки, подсказки или полезные учебные материалы приветствуются.
1 ответ
Вы буквально один персонаж (и, возможно, импорт) от решения:
import monocle.function.all.index
import monocle.std.map._
(
World.town ^|->
Town.houses ^|-?
index((0, 0)) ^|->
House.presentsDelivered
).modify(_ + 1)
Обратите внимание, что я заменил ^|->
непосредственно перед индексом ^|-?
, Это необходимо, потому что index((x, y))
принципиально отличается от World.town
и другие сгенерированные макросом линзы для членов класса дела. Те не могут не указывать на значение, в то время как index
может потерпеть неудачу, если по указанному индексу на карте нет значения. С точки зрения типов Монокль, index((x, y))
является Optional[Houses, House]
, в то время как World.town
это Lens[World, Town]
,
Дополнительные функции в некотором смысле более слабые, чем линзы, и после того, как вы скомпонуете линзу с дополнительной, у вас останутся дополнительные опции, даже если вы создадите больше линз. Итак, вот линза:
World.town ^|-> Town.houses
Но это необязательно:
World.town ^|-> Town.houses ^|-? index((0, 0)) ^|-> House.presentsDelivered
Монокль последовательно использует x ^|-> y
составлять разные типы x
(линзы, опции, обходы и т. д.) с линзами и x ^|-? y
сочинять разные x
с опциями. Я лично нахожу операторов немного запутанными и предпочитаю composeLens
, composeOptional
и т. д., но вкусы различаются, и если вы хотите запомнить операторы, вы можете, по крайней мере, быть уверенными в том, что они используются последовательно - вам просто нужно знать, какой из них вам нужен для данного типа.
Другая потенциальная проблема с вашим кодом заключается в том, что вы не можете просто написать это:
import monocle.function.all.index
val houses: monocle.Optional[Houses, House] = index((0, 0))
Это не скомпилируется само по себе, потому что index
требует экземпляра Index
класс типа для типа, в который он индексируется (в данном случае Map[(Int, Int), House]
, Monocle предоставляет универсальный экземпляр для карт, который будет работать, но вы должны импортировать его:
import monocle.std.map._
Боюсь, у меня нет ужасно хороших предложений для учебных материалов, но вы всегда можете задать вопросы здесь, и канал Monocle Gitter довольно активен.