вопрос не упомянул изменения данных, но когда вам нужно сделать вы быстро обнаружите, что библиотека Scala не имеет инструментов, чтобы сделать это проще (когда данные неизменяемы). Если вы еще этого не испытали, попробуйте написать функцию, которая заменит или изменит value
Cash
, хранящийся в Person
, используя типы, определенные в вопросе.
Как описано в Тони Моррисе Asymmetric Lenses in Scala, объективы являются подходящим решением этой проблемы.
Вот пример того, как мы можем получить доступ и обновлять value
в Cash
с использованием (частичной линзы) реализациями человека Lens
и PLens
от scalaz-seven филиала Scalaz.
Во-первых, некоторые шаблоны: определить экземпляр объектива для каждого поля классов корпуса. A @[email protected] B
означает то же, что и Lens[A, B]
.
val pants: Person @[email protected] Option[Pants] =
lensG(_.pants, p => ps => p.copy(pants = ps))
val pocket: Pants @[email protected] Option[Pocket] =
lensG(_.pocket, ps => p => ps.copy(pocket = p))
val cash: Pocket @[email protected] Option[Cash] =
lensG(_.cash, p => c => p.copy(cash = c))
val value: Cash @[email protected] String =
lensG(_.value, c => v => c.copy(value = v))
Мы не можем составить все эти линзы, однако, поскольку большинство полей завернуты в Option
типов.
Частичные линзы на помощь: они позволяют нам получить доступ и обновлять части структуры, которая не может существовать, такие как Some
значения в Option
, или head
в виде List
.
Мы можем использовать функцию somePLens
от Scalaz 7, чтобы создать частичный объектив, просматривающий каждое дополнительное поле.Однако для составления частичной линзы с одной из наших обычных линз нам нужно получить доступ к эквивалентному экземпляру частичного объектива для обычного объектива, используя метод partial
, который существует на каждом Lens
.
// @-? is an infix type alias for PLens
val someCash: Pocket @-? Cash = cash.partial andThen somePLens
scala> someCash.get(Pocket(Some(Cash("zilch"))))
res1: Option[Cash] = Some(Cash(zilch))
Таким же образом, мы можем создать частичные линзы просмотра денежных средств, хранящиеся на Person
сочинения всех наших линзы partial
экземпляров, и прослаивая экземпляры somePLens
. Здесь я использовал оператора <=<
, псевдоним для andThen
(что эквивалентно compose
с переключенными операндами).
val someCashValue: Person @-? String =
pants.partial <=< somePLens <=<
pocket.partial <=< somePLens <=<
cash.partial <=< somePLens <=<
value.partial
Создание Person
экземпляра, чтобы играть с:
val ben = Person(Some(Pants(Some(Pocket(Some(Cash("zilch")))))))
Использование частичной линзы для доступа к значению наличных денег у меня есть:
scala> someCashValue.get(ben)
res2: Option[String] = Some(zilch)
Использование частичной линзы для изменения значения :
scala> someCashValue.mod(_ + ", zero, nada", ben)
res3: Person = Person(Some(Pants(Some(Pocket(Some(Cash(zilch, zero, nada)))))))
(!) 0
Теперь, если я не носить любые штаны, мы можем видеть, как попытка изменить значение моей наличности не будет иметь никакого эффекта:
scala> val ben = Person(None)
ben: Person = Person(None)
scala> someCashValue.mod(_ + ", zero, nada", ben)
res4: Person = Person(None)
Awesome, спасибо! Подход к пониманию - это именно то, что я пытался сделать, но структура, с которой я работаю, не такая чистая, как пример, который я дал выше. По крайней мере, это подтверждает, что я на правильном пути. –