Есть как минимум 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.
lens
пакет обладает самой богатой функциональностью и документацией, поэтому, если вы не возражаете против его сложности и зависимостей, это путь.