Введение

В данной заметке я вкратце и упрощённо поделюсь своим пониманием общей картины (“Big Picture”) библиотеки lens и вытекающей из него интуиции о линзах.

Интуиция основных операций с линзовыми объектами

Итак, имея линзовый объект (о них - позже), мы можем осуществлять с его помощью четыре фундаментальных действия:

  1. Смотреть на фокус линзы, то есть фокусироваться внутрь структуры данных: view
  2. Модифицировать данные в фокусе линзы, оставляя остальную структуру без изменений: over
  3. Полностью замещать данные в фокусе линзы (может быть, на другой тип), оставляя остальную структуру без изменений: set
  4. Комбинировать линзовые объекты друг с другом, производя новый линзовый объект: (.)

Примеры

Пусть даны типы:

data Sex = Male | Female

data Person = Person
  { _sex :: Sex
  , _age :: Int
  , _siblings :: [Person]
  }

-- автогенерация линзовых объектов для типов выше
makePrisms ''Sex
makeLenses ''Person

s1 = Male
p1 = Person Male 28 []
p2 = Person Female 30 [Person Male 14 [], Person Female 3 [p1]]

Попробуем применить к нему несколько операций (первыми параметрами всегда стоят линзовые объекты):

> view sex p1
Male
> over age (+1) p1
Person {_sex = Male, _age = 29, _siblings = []}
> set sex Female p1
Person {_sex = Female, _age = 28, _siblings = []}

> -- используя другие линзы библиотеки
> toListOf (siblings . traverse . sex) p2
[Male,Female]
> let p2' = over (siblings . mapped . age) (+100) p2
> let p2'' = over (siblings . mapped . siblings . mapped . age) (+1000) p2
> :force p2'
p2' = Person
        Female 30
        [Person Male 114 [],Person Female 103 [Person Male 28 []]]

> :force p2''
p2'' = Person
         Female 30 [Person Male 14 [],Person Female 3 [Person Male 1028 []]]

В двух последних примерах мы удобно “состарили” прямых и непрямых родственников госпожи p2.

Интуиция основных линзовых объектов

Упрощённо говоря, линзовые объекты бывают следующих видов:

  1. Изоморфизмы Iso – биекции между типами, например изоморфизм между (Maybe a) и (Either () a). Изоморфизмы могут быть между любыми типами, имеющими одинаковую форму: например, при наличии изоморфизма между полом человека и булевым типом, мы можем сменить пол всем родственникам: over (siblings . traverse . sex) (view (sexBoolIso . to not . from sexBoolIso)) p2. Я бы сказал, что Iso являются разными отражениями одного и того же объекта.
  2. Линзы Lens – фокус внутрь структуры (на часть целого). Линзы существуют только для типов-произведений (декартовых пар, Person и т.д.), поскольку в фокусе линзы составная (интересующая нас) часть структуры находится всегда. Я бы назвал линзу фокусом на проекцию.
  3. Призмы Prism – фокус на поддерево (вариант, слагаемое суммы) структуры. Призмы существуют только для типов-сумм (типов-перечислений вроде чисел, Bool, списков и т.д.). Фокус призмы, в отличии от линзы, может оказаться “мимо” данных: например фокус на слагаемое True в типе Bool зафейлится на конкретной структуре False :: Bool. Я бы назвал призму фокусом на вариант.
  4. Перевёрнутые призмы Review – фокуc на остальную часть структуры, находящуюся вне фокуса Prism (соответственно, можно восстановить целиком структуру по перевёрнутой призме). Получаются из некоторых призм их переворачиванием (re). Я бы назвал их фокусом на дополнение (complement, теория множеств)
  5. Фолды (Folds) и Траверсалы (Traversals) – фокус на несколько элеменов одновременно (мы можем и не знать, на сколько).

Кстати, в библиотеке lens для многих типов определены синонимы с сильно упрощёнными сигнатурами - их имена заканчиваются “штрихом”:

> :t Iso
Iso :: Iso s t a b -> ReifiedIso s t a b
> :info Iso'
type Iso' s a = Iso s s a a 	-- Defined in ‘Control.Lens.Type’

Пока всё об интуиции.