Модификация карты через монокль

Я хотел попробовать линзы, и библиотека 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 довольно активен.

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