Разве это не избыточно для 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)обман, но это, конечно, не то, что может произойти по ошибке.

Другие вопросы по тегам