Линзы, fclabels, data-accessor - какая библиотека для доступа к структуре и мутации лучше
Существует как минимум три популярные библиотеки для доступа к полям записей и управления ими. Те, о которых я знаю, это: средство доступа к данным, флейбелы и линзы.
Лично я начал с доступа к данным и сейчас ими пользуюсь. Однако недавно в haskell-cafe появилось мнение, что 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
, Я держу этот комментарий, хотя.]
средство доступа к данным несколько более популярно, чем fclabels
отчасти потому, что это Haskell 98. Однако его выбор внутреннего представления заставляет меня немного рвать в рот.
Тип T
он использует для представления объектива внутренне определяется как
newtype T r a = Cons { decons :: a -> r -> (a, r) }
Следовательно, чтобы get
значение объектива, вы должны указать неопределенное значение для аргумента 'a'! Это кажется мне невероятно уродливой и специальной реализацией.
Тем не менее, Хеннинг включил подключение шаблона-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
сформировать Category
(в отличие от lenses
пакет), есть Haskell 98 (в отличие от fclabels
/ lenses
), в здравом уме (в отличие от задней части data-accessor
) и обеспечить немного более эффективную реализацию, data-lens-fd
предоставляет функциональность для работы с MonadState для тех, кто хочет выйти за пределы Haskell 98, а механизм template-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
, для их конкретных типов "а" и "б". Это значительно снижает стоимость усыновления.
Так как вам не нужно фактически использовать пакет для определения новых линз, это снимает большую нагрузку с моих предыдущих опасений по поводу сохранения библиотеки Haskell 98.