Ответ 1
Это замечательный вопрос и потребует немного распаковки.
Я хочу аккуратно исправлять вас в одной точке сразу с места в карьер: тип Setter в пакете lens
по состоянию на последние версии
type Setter s t a b = (a -> Identity b) -> s -> Identity t
Нет Functor
в поле зрения... еще.
Это не отменяет ваш вопрос. Почему не просто тип
type Setter s t a b = (a -> b) -> s -> t
Для этого нам сначала нужно поговорить о lens
.
Lens
A lens
- это тип, который позволяет нам выполнять как работу с геттером, так и сеттер. Эти две комбинации образуют одну красивую функциональную ссылку.
Простой выбор для типа lens
:
type Getter s a = s -> a
type Setter s t a b = (a -> b) -> s -> t
type Lens s t a b = (Getter s a, Setter s t a b)
Этот тип, однако, очень неудовлетворен.
- Он не скомпонован с
.
, который, возможно, является самой лучшей точкой продаж пакетаlens
. - Это скорее неэффективная память для создания множества кортежей, а только для разлома их позже.
- Большая: функции, которые принимают геттеры (например,
view
) и сеттеры (например,over
), не могут воспринимать линзы, потому что их типы настолько разные.
Без этой последней проблемы решить, зачем даже писать библиотеку? Мы не хотели бы, чтобы пользователям приходилось постоянно думать о том, где они находятся в иерархии оптики UML, каждый раз, когда они перемещаются вверх или вниз, корректируют свои функциональные вызовы.
Тогда возникает вопрос о том, существует ли тип, который мы можем записать для lens
так, чтобы он автоматически и Getter
и a Setter
? И для этого мы должны преобразовать типы Getter
и Setter
.
Getter
-
Прежде всего заметим, что
s -> a
эквивалентноforall r. (a -> r) -> s -> r
. Это преобразование в стиль продолжения прохождения далеко не очевидно. Возможно, вы сможете реализовать это преобразование следующим образом: "Функция типаs -> a
- это обещание, которое дано любомуs
, вы можете передать мнеa
. Но это должно быть эквивалентно обещанию, которое дает функцию, которая mapsa
tor
вы можете передать мне функцию, которая отображаетs
вr
." Может быть? Возможно, нет. Здесь может произойти скачок веры. -
Теперь определите
newtype Const r a = Const r deriving Functor
. Обратите внимание, чтоConst r a
совпадает сr
, математически и во время выполнения. -
Теперь обратите внимание, что
type Getter s a = forall r. (a -> r) -> s -> r
можно переписать какtype Getter s t a b = forall r. (a -> Const r b) -> s -> Const r t
. Хотя мы вводили новые типы переменных и душевные муки для себя, этот тип по-прежнему математически идентичен тому, с чего мы начали (s -> a
).
сеттер
-
Определите
newtype Identity a = Identity a
. Обратите внимание, чтоIdentity a
совпадает сa
, математически и во время выполнения. -
Теперь обратите внимание, что
type Setter s t a b = (a -> Identity b) -> s -> Identity t
по-прежнему совпадает с типом, с которого мы начали.
Все вместе
С этой документацией в стороне, можем ли мы объединить сеттеры и геттеры в один тип lens
?
type Setter s t a b = (a -> Identity b) -> s -> Identity t
type Getter s t a b = forall r. (a -> Const r b) -> s -> Const r t
Хорошо, это Haskell, и мы можем абстрагироваться от выбора Identity
или Const
к количественной переменной. Как wiki-видоискатель, все, что Const
и Identity
имеет общее, состоит в том, что каждый из них является Functor
. Затем мы выбираем это как своего рода унификацию для этих типов:
type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
(Есть и другие причины, чтобы выбрать Functor
, например, чтобы доказать законы функциональных ссылок, используя свободные теоремы, но мы немного поговорим здесь о времени.) Это forall f
похоже на forall r
. выше - это позволяет потребителям типа выбирать, как заполнить переменную. Заполните Identity
, и вы получите сеттер. Заполните Const a
, и вы получите получателя. Это было путем выбора небольших и осторожных преобразований на том пути, по которому мы смогли достичь этого момента.
Предостережения
Возможно, важно отметить, что этот вывод не является оригинальной мотивацией для пакета lens
. Как объясняется страницами wiki-страниц деривации, вы можете начать с интересного поведения (.)
с определенными функциями и оттуда оптика оттуда. Но я думаю, что этот путь, который мы вырезали, немного лучше объясняет вопрос, который вы задали, и это был большой вопрос, который я тоже начинал. Я также хочу передать вам объектив поверх чая, который предоставляет еще один вывод.
Я думаю, что эти множественные деривации - это хорошая вещь и своего рода измерительный щуп для здорового дизайна lens
. То, что мы можем прийти к тому же изящному решению под разными углами, означает, что эта абстракция надежна и хорошо поддерживается различными интуициями и математикой.
Я также немного солгал о типе Setter в недавнем lens
. Это на самом деле
type Setter s t a b = forall f. Settable f => (a -> f b) -> s -> t b
Это еще один пример абстрагирования типа более высокого порядка в оптических типах, чтобы предоставить пользователю библиотеки лучший опыт. Почти всегда f
будет создаваться на Identity
, так как существует instance Settable Identity
. Однако время от времени вам может понадобиться передать сеттеры функции backwards
, которая фиксирует f
как Backwards Identity
. Вероятно, мы можем классифицировать этот абзац как "более подробную информацию о lens
, чем вы, вероятно, хотели бы знать".