Что такое Призмы?
Я пытаюсь достичь более глубокого понимания библиотеки lens
, поэтому я играю с типами, которые он предлагает. У меня уже был некоторый опыт работы с объективами, и я знаю, насколько они эффективны и удобны. Поэтому я перешел к Призмам, и я немного потерялся. Кажется, что призмы допускают две вещи:
- Определение того, принадлежит ли сущность к определенной ветки типа суммы, и, если это так, фиксирует базовые данные в кортеже или синглете.
- Уничтожение и восстановление объекта, возможно, его модификация.
Первая точка кажется полезной, но обычно не нужны все данные из объекта, а ^?
с обычными объективами позволяет получить Nothing
если поле, о котором идет речь, не принадлежит ветке, которую представляет сущность, точно так же, как и призм.
Второй момент... Я не знаю, возможно, использовал?
Итак, вопрос: что я могу сделать с Призмой, которую я не могу с другой оптикой?
Редактировать: спасибо всем за отличные ответы и ссылки для дальнейшего чтения! Хотел бы я принять их всех.
Ответы
Ответ 1
Линзы характеризуют отношения has-a; Призмы характеризуют отношения is-a.
Lens sa
говорит, что " s
имеет a
"; у него есть методы, чтобы получить ровно один a
из s
и переписать ровно один a
в s
. Prism sa
говорит: " a
is s
"; у него есть методы для повышения уровня a
до s
и (попытки) сбрасывать s
в a
.
Включение этой интуиции в код дает вам привычную "get-set" (или "costate comonad cocgebra") формулировку линз,
data Lens s a = Lens {
get :: s -> a,
set :: a -> s -> s
}
и "upcast-downcast" представление призмы,
data Prism s a = Prism {
up :: a -> s,
down :: s -> Maybe a
}
up
вводит a
в s
(без добавления какой-либо информации), а down
проверяет, является ли s
a
.
В lens
up
review
и down
preview
. Нет конструктора Prism
; вы используете умный конструктор prism'
.
Что вы можете сделать с Prism
? Типы вложений и проектов!
_Left :: Prism (Either a b) a
_Left = Prism {
up = Left,
down = either Just (const Nothing)
}
_Right :: Prism (Either a b) b
_Right = Prism {
up = Right,
down = either (const Nothing) Just
}
Объективы не поддерживают это - вы не можете написать Lens (Either ab) a
потому что вы не можете реализовать get :: Either ab → a
. Как практический вопрос, вы можете написать Traversal (Either ab) a
, но это не позволяет вам создать Either ab
из a
- он позволит вам только перезаписать a
который уже существует.
Кроме того: я думаю, что этот тонкий момент об Traversal
является источником вашей путаницы в отношении частичных полей записей.
^?
с обычными объективами позволяет получить Nothing
если поле, о котором идет речь, не принадлежит ветке, представляющей сущность
Используя ^?
с реальным Lens
никогда не вернется Nothing
, потому что Lens sa
идентифицирует ровно один a
внутри s
. При столкновении с частичным полем записи,
data Wibble = Wobble { _wobble :: Int } | Wubble { _wubble :: Bool }
makeLenses
генерирует Traversal
, а не Lens
.
wobble :: Traversal' Wibble Int
wubble :: Traversal' Wibble Bool
Для примера, как Prism
может применяться на практике, посмотрите на Control.Exception.Lens
, который предоставляет коллекцию Prism
в расширяемой иерархии Exception
Haskell. Это позволяет выполнять тесты типа времени выполнения в SomeException
и вводить определенные исключения в SomeException
.
_ArithException :: Prism' SomeException ArithException
_AsyncException :: Prism' SomeException AsyncException
-- etc.
(Это несколько упрощенные версии реальных типов. В действительности эти призмы являются перегруженными методами класса.)
Подводя итог, Lens
es и Prism
вместе кодируют два основных инструментария проектирования объектно-ориентированного программирования, составления и подтипирования. Lens
es - это первоклассная версия Java .
и =
, а Prism
- первоклассная версия Java instanceof
и неявное повышение.
Одним из плодотворных способов мышления о Lens
es является то, что они дают вам способ расщепления композита s
на сфокусированное значение a
и некоторый контекст c
. псевдокод:
type Lens s a = exists c. s <-> (a, c)
В этой структуре Prism
дает вам возможность взглянуть на s
как на a
и на некоторый контекст c
.
type Prism s a = exists c. s <-> Either a c
(Я оставлю это вам, чтобы убедить себя, что они изоморфны простым представлениям, которые я продемонстрировал выше. Попробуйте реализовать get
/set
/up
/down
для этих типов!)
В этом смысле Prism
является Lens
co-. Either
есть категорическое двойственное (,)
; Prism
является категорическим двойником Lens
.
Вы также можете наблюдать эту двойственность в "profunctor оптики" композиции - Strong
и Choice
двойственны.
type Lens s t a b = forall p. Strong p => p a b -> p s t
type Prism s t a b = forall p. Choice p => p a b -> p s t
Это более или менее представление, которое использует lens
, потому что эти Lens
и Prism
очень сложны. Вы можете составить Prism
для получения большей Prism
(" a
является s
, которая является p
"), используя (.)
; составляя Prism
с Lens
дает вам Traversal
.
Ответ 2
Я просто написал сообщение в блоге, которое могло бы помочь построить некоторую интуицию в отношении Призмы: Призмы - это конструкторы (Объективы - это поля). http://oleg.fi/gists/posts/2018-06-19-prisms-are-constructors.html
Призмы могут быть представлены как первоклассное сопоставление образцов, но это одностороннее представление. Я бы сказал, что они обобщенные конструкторы, хотя, возможно, чаще всего используются для сопоставления шаблонов, чем для фактического построения.
Важным свойством конструкторов (и законных призм) является их инъективность. Хотя в обычных законах призмы не указано, что непосредственно можно определить свойство приемистости.
Чтобы представить документацию lens
-library, законы призм:
Во-первых, если я review
значение с Prism
а затем preview
, я верну его:
preview l (review l b) ≡ Just b
Во-вторых, если вы можете извлечь значение a с помощью Prism
l
из значения s
, то значение s
полностью описывается l
и a
:
preview l s ≡ Just a ⇒ review l a ≡ s
На самом деле, первого закона достаточно, чтобы доказать инъективность конструкции через Prism
:
review l x ≡ review l y ⇒ x ≡ y
Доказательство прямолинейно:
review l x ≡ review l y
-- x ≡ y -> f x ≡ f y
preview l (review l x) ≡ preview l (review l y)
-- rewrite both sides with the first law
Just x ≡ Just y
-- injectivity of Just
x ≡ y
Мы можем использовать свойство инъективности в качестве дополнительного инструмента в инструментальной панели эквационального рассуждения. Или мы можем использовать его в качестве простой собственности, чтобы проверить, является ли что-то законной Prism
. Проверка проста, так как мы только review
часть Prism
. Многие интеллектуальные конструкторы, которые, например, нормализуют входные данные, не являются законными призмами.
Пример использования case-insensitive
:
-- Bad!
_CI :: FoldCase s => Prism' (CI s) s
_CI = prism' ci (Just . foldedCase)
λ> review _CI "FOO" == review _CI "foo"
True
λ> "FOO" == "foo"
False
Также нарушен первый закон:
λ> preview _CI (review _CI "FOO")
Just "foo"
Ответ 3
В дополнение к другим отличным ответам, я чувствую, что Iso
обеспечивает хорошую точку зрения для рассмотрения этого вопроса.
-
Там быть некоторые i :: Iso' sa
означает, что если у вас есть s
значение вы (практически) есть a
значение, и наоборот. Iso'
дает вам две функции преобразования, view я :: s → a
и review я :: a → s
которые гарантированы как успешными, так и без потерь.
-
Там, где l :: Lens' sa
означает, что у вас есть s
вас также есть a
, но не наоборот. view l :: s → a
может отбросить информацию на этом пути, поскольку преобразование не требуется без потерь, и поэтому вы не можете пойти другим путем, если все, что у вас есть, - это a
(cf. set l :: a → s → s
, что также требует s
в дополнение к значению a
, чтобы обеспечить недостающую информацию).
- Если есть какой-то
p :: Prism' sa
значит, если у вас есть значение s
вас также может быть a
, но нет никаких гарантий. preview p :: s → Maybe a
конвертации preview p :: s → Maybe a
не гарантируется успех. Тем не менее, у вас есть другое направление, review p :: a → s
.
Другими словами, Iso
является обратимым и всегда преуспевает. Если вы откажетесь от требования к обратимости, вы получите Lens
; если вы откажетесь от гарантии успеха, вы получите Prism
. Если вы бросите оба, вы получите аффинный обход (который не входит в объектив как отдельный тип), и если вы сделаете шаг дальше и откажитесь от того, что у вас будет не более одной цели, вы получите Traversal
. Это отражено в одном из алмазов иерархии подтипов объектива:
Traversal
/ \
/ \
/ \
Lens Prism
\ /
\ /
\ /
Iso