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

173

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

Лично я начал с доступа к данным и сейчас ими пользуюсь. Однако недавно в haskell-cafe появилось мнение, что fclabels лучше.

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

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

Ответы:

200

Есть как минимум 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. Я держу этот комментарий, хотя.]

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

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

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

Следовательно, для getполучения значения объектива необходимо указать неопределенное значение для аргумента «а»! Это кажется мне невероятно уродливой и специальной реализацией.

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

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

линзы

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

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

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

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

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

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

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

Данные линзы

[Редактировать: Начиная с 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определении, особенно, когда методы доступа связаны друг с другом.

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

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

Этот подход был впервые отмечен и описан Расселом О'Коннором в Functorтом, Lensчто он Applicativeесть Biplate: Представляем Multiplate, и его блог был основан на препринте Джереми Гиббонс.

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

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

Обновление от 28 июня 2012 года: Другие стратегии внедрения объективов

Изоморфизм линз

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

Данный тип для изоморфизмов

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

так, чтобы действительные члены удовлетворяли hither . yon = 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Я определил выше _2на самом деле LensFamily.

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

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

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

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

Эдвард КМЕТТ
источник
28
Мне нравится fclabels за его оптимистичный подход:->
Tener
10
Важна ли совместимость с Haskell 1998? Потому что это облегчает разработку компилятора? И разве мы не должны вместо этого говорить о Haskell 2010?
Яирчу
55
о нет! Я был первоначальным автором data-accessor, а затем передал его Хеннингу и перестал обращать внимание. a -> r -> (a,r)Представление также делает меня неудобно, и мое первоначальное внедрение было так же , как ваш Lensтип. Heeennnninngg !!
Луки
5
Yairchu: в основном ваша библиотека может иметь возможность работать с компилятором, отличным от ghc. Ни у кого больше нет шаблона Haskell. 2010 не добавляет ничего актуального здесь.
Эдвард КМЕТТ