Разве это не избыточно для Control.Lens.Setter для обёртывания типов в функторы?
Я смотрю Control.Lens
вступительное видео.
Это заставляет меня задуматься, зачем это нужно Setter
типа обернуть вещи в функторы.
Это (примерно) определено так:
type Control.Lens.Setter s t a b = (Functor f) => (a -> f a) -> s -> f t
Допустим, у меня есть данные под названием Point
это определяется так:
data Point = Point { _x :: Int, _y :: Int } deriving Show
Тогда я могу написать свой собственный xlens
как это:
type MySetter s t a b = (a -> b) -> s -> t
xlens :: MySetter Point Point Int Int
xlens f p = p { _x = f (_x p) }
И я могу использовать это так:
p = Point 100 200
xlens (+1) p -- Results in Point { _x = 101, _y = 200 }
Используя Control.Lens
Тот же эффект достигается за счет:
over x (+1) p
где стоит следующее:
x :: Functor f => (Int -> f Int) -> Point -> f Point
over :: Setter Point Point Int Int -> (Int -> Int) -> Point -> Point
Итак, мой вопрос, поскольку тот же эффект может быть достигнут более простым способом, почему Control.Lens
оборачивает вещи в функторы? Это выглядит избыточным для меня, так как мой xlens
делает так же, как Control.Lens
"s over x
,
Просто для записи, я также могу связать линзы таким же образом:
data Atom = Atom { _element :: String, _pos :: Point } deriving Show
poslens :: MySetter Atom Atom Point Point
poslens f a = a { _pos = f (_pos a) }
a = Atom "Oxygen" p
(poslens . xlens) :: (Int -> Int) -> Atom -> Atom
(poslens . xlens) (+1) a -- Results in Atom "Oxygen" (Point 101 200)
2 ответа
Это замечательный вопрос и потребует немного распаковки.
Я хочу аккуратно исправить вас в одной точке сразу же: тип Сеттера в 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
это тип, который позволяет нам выполнять операции получения и установки. Эти два вместе составляют одну красивую функциональную ссылку.
Простой выбор для 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
и Setter
? И для этого мы должны преобразовать типы Getter
а также Setter
,
добытчик
Первое замечание, что
s -> a
эквивалентноforall r. (a -> r) -> s -> r
, Это превращение в стиль прохождения продолжения далеко не очевидно. Вы можете быть в состоянии интуитивно понять это преобразование так: "Функция типаs -> a
это обещание, которое дано любомуs
ты можешь передать мнеa
, Но это должно быть эквивалентно обещанию, которое дано функции, которая отображаетa
вr
Вы можете передать мне функцию, которая отображает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
к количественной переменной. Как говорится в объективе вики, все это 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
пакет. Как объясняет вики-страница Derivation, вы можете начать с интересного поведения (.)
с определенными функциями и выдрать оптику оттуда. Но я думаю, что этот путь, который мы выбрали, немного лучше объясняет заданный вами вопрос, и это был большой вопрос, который я тоже начал. Я также хочу отослать вас к объективу за чаем, который обеспечивает еще один вывод.
Я думаю, что эти множественные деривации являются хорошей вещью и своего рода щупом для здоровья lens
дизайн. То, что мы можем прийти к одному и тому же элегантному решению под разными углами, означает, что эта абстракция является надежной и хорошо поддерживается различными интуициями и математиками.
Я также соврал немного о типе сеттера в последнее время. 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
чем вы, вероятно, хотели знать.
В некотором смысле, причина lens
Обертки-сеттеры в возвращаемых значениях функторов заключаются в том, что в противном случае они были бы слишком мощными.
Фактически, когда используется установщик, функтор будет создан для Identity
во всяком случае, что точно так же, как ваша предложенная подпись. Однакореализация сеттера не должна использовать этот факт. С вашей подписью я мог бы написать что-то вроде
zlens :: MySetter Point Point Int Int
zlens _f p = p -- no z here!
Ну, это просто невозможно сFunctor
на основании подписи, потому чтоzlens
нужно будет повсеместно определить количественно по функтору, он не может знать, как ввести результат в f
обертка. Единственный способ получить результат типа функтора - это сначала применить функцию-установщик к полю правильного типа!
Итак, это просто хорошая бесплатная теорема†.
На практике нам нужна оболочка функтора длясовместимости. Хотя вы можете определять сеттеры без этой обертки, это не возможно для геттеров, так как они используют Const
скорее, чемIdentity
и нужен дополнительный полиморфизм в первом аргументе этого конструктора типа. Требуя такой обертки для всех видов линз (только с различными классовыми ограничениями), мы можем использовать одни и те же комбинаторы для всех них, однако система типов всегда будет сворачивать функциональность до того, какие функции действительно применимы к ситуации.
†Думая об этом, гарантия на самом деле не очень сильна... Я все еще могу подорвать это с некоторымиfmap (const old)
обман, но это, конечно, не то, что может произойти по ошибке.