Я знаю как минимум 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
, отчасти потому, что он < / em> Haskell 98. Однако его выбор внутреннего представления заставляет меня немного рвать во рту.
Тип T
, который он использует для представления линзы, внутренне определяется как
newtype T r a = Cons { decons :: a -> r -> (a, r) }
Следовательно, чтобы get
значение линзы, вы должны передать неопределенное значение для аргумента 'a'! Это кажется мне невероятно уродливой и специальной реализацией.
Тем не менее, Хеннинг включил сантехнику template-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 в основном пакете это моя наименее любимая реализация.
data-lens
[Edit: Начиная с версии 1.8.0, они были перемещены из пакета comonad-transformers в data-lens]
Мой пакет data-lens
предоставляет линзы с точки зрения Store 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 сложных закона для объектива l
в 2 приятных безоточечных эквивалента:
extract . l = id
duplicate . l = fmap l . l
Этот подход был впервые отмечен и описан в статье Рассела О'Коннора Functor
is to Lens
as Applicative
is to Biplate
: Introduction Multiplate < / a> и был в блоге about на основе препринта Джереми Гиббонса.
Он также включает ряд комбинаторов для работы только с линзами и некоторые стандартные линзы для контейнеров, например Data.Map
.
Таким образом, линзы в data-lens
образуют Category
(в отличие от пакета lenses
), относятся к Haskell 98 (в отличие от _37 _ / _ 38_), разумны (в отличие от задней части data-accessor
) и обеспечивают немного более эффективную реализацию, _ 40_ предоставляет функциональные возможности для работы с MonadState для тех, кто хочет выйти за пределы Haskell 98, а также механизм шаблонов haskell теперь доступен через data-lens-template
.
Обновление от 28.06.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)
Я написал библиотеку, которая включает линзы, семейства линз и другие обобщения, включая геттеры, сеттеры, складки и обходы. Он доступен для взлома в виде пакета lens
.
Опять же, большим преимуществом этого подхода является то, что сопровождающие библиотеки могут фактически создавать линзы в этом стиле в ваших библиотеках без какой-либо зависимости библиотеки линз, просто предоставляя функции с типом Functor f => (b -> f b) -> a -> f a
для их конкретных типов 'a' и 'b'. Это значительно снижает стоимость усыновления.
Поскольку на самом деле вам не нужно использовать пакет для определения новых линз, это снимает большую нагрузку с моих прежних опасений по поводу сохранения библиотеки Haskell 98.
person
Community
schedule
24.04.2011
lens
имеет самые богатые функциональные возможности и документацию, поэтому, если вы не против его сложность и зависимости, это правильный путь. - person modular   schedule 05.10.2016