Объективы, fclabels, data-accessor - какая библиотека для доступа к структуре и мутации лучше

Существует не менее трех популярных библиотек для доступа и обработки полей записей. Те, о которых я знаю, являются: аксессуар данных, флебели и линзы.

Лично я начал с доступа к данным и теперь использую их. Однако в последнее время на haskell-кафе было мнение о том, что fclabels превосходят.

Поэтому я заинтересован в сравнении этих трех (и, возможно, больше) библиотек.

Ответы

Ответ 1

Есть, по крайней мере, 4 библиотеки, которые я знаю о предоставлении объективов.

Понятие объектива заключается в том, что оно обеспечивает нечто, изоморфное

data Lens a b = Lens (a -> b) (b -> a -> a)

обеспечивая две функции: геттер и сеттер

get (Lens g _) = g
put (Lens _ s) = s

в соответствии с тремя законами:

Во-первых, если вы что-то помещаете, вы можете вернуть его обратно

get l (put l b a) = b 

Во-вторых, получение и затем настройка не меняют ответ

put l (get l a) a = a

И в-третьих, дважды положить то же самое, что положить один раз, или, скорее, второй выигрыш.

put l b1 (put l b2 a) = put l b1 a

Обратите внимание, что система типов недостаточно для проверки этих законов для вас, поэтому вам необходимо обеспечить их самостоятельно независимо от того, какую именно реализацию объектива вы используете.

Многие из этих библиотек также предоставляют кучу дополнительных комбинаторов сверху и, как правило, некоторые формы шаблона haskell для автоматической генерации объективов для полей простых типов записей.

Имея это в виду, мы можем обратиться к различным реализациям:

Реализации

fclabels

fclabels, пожалуй, наиболее легко рассуждает о библиотеках объективов, потому что его a :-> b можно напрямую перевести на вышеуказанный тип. Он предоставляет экземпляр категории для (:->) который полезен, поскольку он позволяет создавать линзы. Он также предоставляет тип беззаконных Point который обобщает понятие используемой здесь линзы и некоторую сантехнику для работы с изоморфизмами.

Одним из препятствий для принятия fclabels является то, что основной пакет включает в себя сантехнику шаблона-haskell, поэтому пакет не Haskell 98, а также требует (довольно непротиворечивое) расширение TypeOperators.

данных аксессор

[Изменение: data-accessor больше не использует это представление, но перешел в форму, аналогичную форме data-lens. Тем не менее, я придерживаюсь этого комментария.]

data-accessor несколько более популярен, чем fclabels, отчасти потому, что это Haskell 98. Однако выбор внутреннего представления заставляет меня немного поднять у меня в рот.

Тип T он использует для представления объектива, внутренне определяется как

newtype T r a = Cons { decons :: a -> r -> (a, r) }

Следовательно, чтобы get значение объектива, вы должны представить неопределенное значение аргумента "a"! Это поражает меня как невероятно уродливую и специальную реализацию.

Тем не менее, Хеннинг включил сантехнику шаблона-haskell, чтобы автоматически генерировать аксессуры для вас в отдельном пакете " data-accessor-template ".

Он имеет преимущество прилично большого набора пакетов, которые уже используют его, будучи Haskell 98, и предоставляет Category экземпляр Category, поэтому, если вы не обращаете внимание на то, как делается колбаса, этот пакет на самом деле довольно разумный выбор.

линзы

Далее, есть пакет линз, в котором отмечается, что объектив может обеспечить гомоморфизм государственного монада между двумя государственными монадами, определяя линзы непосредственно как такие гомоморфизмы монады.

Если бы он действительно беспокоился о предоставлении типа для своих линз, у них был бы тип ранга 2, например:

newtype Lens s t = Lens (forall a. State t a -> State s a)

В результате мне не нравится такой подход, так как он бесполезно выводит вас из Haskell 98 (если вы хотите, чтобы тип предоставлял ваши линзы в абстракте) и лишил вас экземпляра Category для линз, что позволило бы вы их сочиняете . , Для реализации также требуются классы с несколькими параметрами.

Обратите внимание, что все другие библиотеки линз, упомянутые здесь, предоставляют некоторый комбинатор или могут использоваться для обеспечения такого же эффекта фокализации состояния, поэтому ничего не получается путем кодирования вашего объектива прямо таким образом.

Кроме того, побочные условия, заявленные в начале, на самом деле не имеют приятного выражения в этой форме. Как и в случае с fclabels, это обеспечивает метод template-haskell для автоматического создания объективов для типа записи непосредственно в основном пакете.

Из-за отсутствия экземпляра Category, барочной кодировки и требования шаблона-haskell в основном пакете, это моя наименее любимая реализация.

Данные линзы

[Edit: Начиная с версии 1.8.0, они перешли от пакета comonad-transformers к объективу данных]

Мой пакет data-lens предоставляет объективы с точки зрения магазина comonad.

newtype Lens a b = Lens (a -> Store b a)

где

data Store b a = Store (b -> a) b

Расширенное это эквивалентно

newtype Lens a b = Lens (a -> (b, b -> a))

Вы можете просмотреть это как факторинг общего аргумента от геттера и сеттера, чтобы вернуть пару, состоящую из результата извлечения элемента, и сеттер, чтобы вернуть новое значение. Это дает вычислительную выгоду, что "сеттер", здесь можно переработать часть работы, используемой для получения значения, делая более эффективную операцию "модификации", чем в определении fclabels, особенно при fclabels аксессуаров.

Существует также хорошее теоретическое обоснование этого представления, потому что подмножество значений "Объектив", которые удовлетворяют 3 законам, изложенным в начале этого отклика, являются именно теми объективами, для которых обернутая функция является "комонадной коалгеброй" для магазина comonad, Это трансформирует 3 волосатых закона для объектива l до 2 хороших эквивалентов:

extract . l = id
duplicate . l = fmap l . l

Этот подход был впервые отмечен и описан в Russell O'Connor Functor для Lens как Applicative для Biplate: введение в Multiplate и был основан на блоге на основе препринта Джереми Гиббонса.

Он также включает в себя ряд комбинаторов для работы с линзами строго и некоторые фондовые линзы для контейнеров, таких как Data.Map.

Таким образом, линзы в объективах data-lens образуют Category (в отличие от пакета lenses), являются Haskell 98 (в отличие от fclabels/lenses), являются здравомыслящими (в отличие от задней части устройства data-accessor к data-accessor) и обеспечивают немного более эффективную реализацию, data-lens-fd предоставляет функциональные возможности для работы с MonadState для тех, кто хочет выйти за пределы Haskell 98, а механизм шаблона-haskell теперь доступен с помощью data-lens-template.

Обновление 6/28/2012: Другие стратегии реализации объективов

Объективы изоморфизма

Есть два других кодирования объектива, которые стоит рассмотреть. Первый дает хороший теоретический способ просмотра объектива как способ разбить структуру на значение поля и "все остальное".

Для типа изоморфизмов

data Iso a b = Iso { hither :: a -> b, yon :: b -> a }

так что действительные члены удовлетворяют hither. yon = id hither. yon = id и yon. hither = id yon. hither = id

Мы можем представить объектив с:

data Lens a b = forall c. Lens (Iso a (b,c))

Они в первую очередь полезны как способ думать о смысле линз, и мы можем использовать их в качестве инструмента для объяснения других линз.

Объективы Ван Лаарховена

Мы можем моделировать объективы так, чтобы их можно было скомпоновать с (.) И id, даже без экземпляра Category, используя

type Lens a b = forall f. Functor f => (b -> f b) -> a -> f a

как тип для наших линз.

Тогда определение объектива так же просто, как:

_2 f (a,b) = (,) a <$> f b

и вы можете проверить для себя, что состав композиции - состав линз.

Недавно я написал о том, как можно дальше обобщать линзы ван Лаарховена, чтобы получить семейства линз, которые могут изменять типы полей, просто обобщая эту подпись на

type LensFamily a b c d = forall f. Functor f => (c -> f d) -> a -> f b

У этого есть неудачное последствие, что лучший способ говорить о линзах - использовать полиморфизм 2-го ранга, но вам не нужно использовать эту подпись непосредственно при определении объективов.

Lens I, определенный выше для _2 на самом деле является LensFamily.

_2 :: Functor f => (a -> f b) -> (c,a) -> f (c, b)

Я написал библиотеку, которая включает в себя линзы, семейство объективов и другие обобщения, включая геттеры, сеттеры, складки и обходы. Он доступен для взлома как пакет lens.

Опять же, большое преимущество этого подхода заключается в том, что разработчики библиотек могут фактически создавать объективы в этом стиле в ваших библиотеках, не влекущая за собой никакой зависимости библиотеки объективов, просто поставляя функции с помощью типа Functor f => (b → fb) → a → fa, для их конкретных типов "a" и "b". Это значительно снижает стоимость усыновления.

Поскольку вам не нужно на самом деле использовать пакет для определения новых объективов, это требует значительного давления на мои предыдущие проблемы с хранением библиотеки Haskell 98.