В вопросе не упоминалось изменение данных, но когда вам нужно это сделать, вы быстро обнаруживаете, что в библиотеке Scala нет инструментов, чтобы упростить это (когда данные неизменяемы). Если вы еще не испытали этого, попробуйте написать функцию, которая заменит или изменит value
из Cash
, удерживаемого Person
, используя типы, определенные в вопросе.
Как описано в асимметричных линзах в Scala Тони Морриса, линзы являются подходящими решение этой проблемы.
Вот пример того, как мы можем получить доступ и обновить value
Cash
человека с помощью реализаций Lens
и PLens
(частичная линза) из скалаз-семерка ветка Скалаза.
Во-первых, некоторый шаблон: определите экземпляр Lens для каждого поля классов case. A @-@ B
означает то же, что и Lens[A, B]
.
val pants: Person @-@ Option[Pants] =
lensG(_.pants, p => ps => p.copy(pants = ps))
val pocket: Pants @-@ Option[Pocket] =
lensG(_.pocket, ps => p => ps.copy(pocket = p))
val cash: Pocket @-@ Option[Cash] =
lensG(_.cash, p => c => p.copy(cash = c))
val value: Cash @-@ 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)))))))
Теперь, если я не ношу штаны (!), Мы можем увидеть, что попытка изменить стоимость моих денег не будет иметь никакого эффекта:
scala> val ben = Person(None)
ben: Person = Person(None)
scala> someCashValue.mod(_ + ", zero, nada", ben)
res4: Person = Person(None)
person
Ben James
schedule
02.04.2012