Подтвердить что ты не робот

Какие линзы используются/полезны?

Я не могу найти никакого объяснения того, какие линзы используются в практических примерах. Этот короткий абзац на странице Hackage является ближайшим, который я нашел:

Эти модули обеспечивают удобный способ доступа и обновления элементов структуры. Он очень похож на Data.Accessors, но немного более общий и имеет меньше зависимостей. Мне особенно нравится, как чисто он обрабатывает вложенные структуры в государственных монадах.

Итак, для чего они используются? Какие преимущества и недостатки они имеют в отношении других методов? Зачем они нужны?

4b9b3361

Ответ 1

Они предлагают чистую абстракцию над обновлениями данных и никогда не нужны "нужны". Они просто позволяют вам рассуждать о проблеме по-другому.

В некоторых императивных/ "объектно-ориентированных" языках программирования, таких как C, у вас есть знакомая концепция некоторой коллекции значений (назовем их "structs" ) и способы маркировки каждого значения в коллекции (ярлыки обычно называются "поле" ). Это приводит к следующему определению:

typedef struct { /* defining a new struct type */
  float x; /* field */
  float y; /* field */
} Vec2;

typedef struct {
  Vec2 col1; /* nested structs */
  Vec2 col2;
} Mat2;

Затем вы можете создать значения этого нового типа, например:

Vec2 vec = { 2.0f, 3.0f };
/* Reading the components of vec */
float foo = vec.x;
/* Writing to the components of vec */
vec.y = foo;

Mat2 mat = { vec, vec };
/* Changing a nested field in the matrix */
mat.col2.x = 4.0f;

Аналогично в Haskell у нас есть типы данных:

data Vec2 =
  Vec2
  { vecX :: Float
  , vecY :: Float
  }

data Mat2 =
  Mat2
  { matCol1 :: Vec2
  , matCol2 :: Vec2
  }

Затем этот тип данных используется следующим образом:

let vec  = Vec2 2 3
    -- Reading the components of vec
    foo  = vecX vec
    -- Creating a new vector with some component changed.
    vec2 = vec { vecY = foo }

    mat = Mat2 vec2 vec2

Однако в Haskell нет простого способа изменения вложенных полей в структуре данных. Это связано с тем, что вам нужно заново создать все объекты обтекания значения, которое вы меняете, потому что значения Haskell неизменяемы. Если у вас есть матрица, подобная приведенной выше в Haskell, и вы хотите изменить верхнюю правую ячейку в матрице, вы должны написать это:

    mat2 = mat { matCol2 = (matCol2 mat) { vecX = 4 } }

Это работает, но выглядит неуклюже. Итак, что кто-то придумал, в основном это: если вы группируете две вещи вместе: "getter" значения (например, vecX и matCol2 выше) с соответствующей функцией, которая, учитывая структуру данных, что геттер принадлежит, может создать новую структуру данных с измененным значением, вы можете сделать много аккуратного материала. Например:

data Data = Data { member :: Int }

-- The "getter" of the member variable
getMember :: Data -> Int
getMember d = member d

-- The "setter" or more accurately "updater" of the member variable
setMember :: Data -> Int -> Data
setMember d m = d { member = m }

memberLens :: (Data -> Int, Data -> Int -> Data)
memberLens = (getMember, setMember)

Существует множество способов применения линз; для этого текста предположим, что объектив подобен выше:

type Lens a b = (a -> b, a -> b -> a)

т.е. это комбинация геттера и сеттера для некоторого типа a, у которого есть поле типа b, поэтому memberLens выше будет Lens Data Int. Что это позволяет нам делать?

Хорошо, сначала сделайте две простые функции, которые извлекают геттеры и сеттеры из объектива:

getL :: Lens a b -> a -> b
getL (getter, setter) = getter

setL :: Lens a b -> a -> b -> a
setL (getter, setter) = setter

Теперь мы можем начать абстрагироваться над вещами. Вернемся к ситуации выше, чтобы мы хотели изменить значение "две истории в глубину". Мы добавляем структуру данных с другой линзой:

data Foo = Foo { subData :: Data }

subDataLens :: Lens Foo Data
subDataLens = (subData, \ f s -> f { subData = s }) -- short lens definition

Теперь добавим функцию, которая состоит из двух объективов:

(#) :: Lens a b -> Lens b c -> Lens a c
(#) (getter1, setter1) (getter2, setter2) =
    (getter2 . getter1, combinedSetter)
    where
      combinedSetter a x =
        let oldInner = getter1 a
            newInner = setter2 oldInner x
        in setter1 a newInner

Код довольно быстро написан, но я думаю, что он ясно, что он делает: геттеры просто сгруппированы; вы получаете внутреннее значение данных, а затем читаете его поле. Установщик, когда предполагается изменить значение a с новым значением внутреннего поля x, сначала извлекает старую внутреннюю структуру данных, устанавливает ее внутреннее поле и затем обновляет внешнюю структуру данных новыми внутренними данными структура.

Теперь сделаем функцию, которая просто увеличивает значение объектива:

increment :: Lens a Int -> a -> a
increment l a = setL l a (getL l a + 1)

Если у нас есть этот код, становится ясно, что он делает:

d = Data 3
print $ increment memberLens d -- Prints "Data 4", the inner field is updated.

Теперь, поскольку мы можем создавать линзы, мы также можем сделать это:

f = Foo (Data 5)
print $ increment (subDataLens#memberLens) f
-- Prints "Foo (Data 6)", the innermost field is updated.

То, что делают все пакеты для объективов, заключается в том, чтобы обернуть эту концепцию линз - группировку "сеттер" и "геттер" в аккуратный пакет, который делает их простыми в использовании. В конкретной реализации объектива можно было бы написать:

with (Foo (Data 5)) $ do
  subDataLens . memberLens $= 7

Итак, вы очень близки к версии кода C; становится очень легко изменять вложенные значения в дереве структур данных.

Объективы - это не что иное, как простой способ изменения частей некоторых данных. Поскольку из-за них становится легче рассуждать о некоторых концепциях, они широко используются в ситуациях, когда у вас есть огромные наборы структур данных, которые должны взаимодействовать друг с другом по-разному.

В отношении плюсов и минусов линз см. недавний вопрос здесь о SO.

Ответ 2

Объективы предоставляют удобные способы редактирования структур данных в едином, композиционном виде.

Многие программы создаются в следующих операциях:

  • просмотр компонента (возможно вложенной) структуры данных
  • поля обновления (возможно, вложенных) структур данных

Объективы обеспечивают языковую поддержку для просмотра и редактирования структур таким образом, чтобы ваши изменения были согласованными; что редактирование может быть легко составлено; и что тот же код может использоваться для просмотра частей структуры, а также для обновления частей структуры.

Объективы таким образом облегчают запись программ из представлений на структуры; и от структур обратно к представлениям (и редакторам) для этих структур. Они убирают множество беспорядков в аксессурах и сеттерах записей.

Pierce et al. популяризированных линз, например. в своей статье Quotient Lenses, а реализации для Haskell теперь широко используются (например, fclabels и данные, аксессоров).

Для конкретных вариантов использования рассмотрим:

  • графические пользовательские интерфейсы, где пользователь редактирует информацию структурированным способом.
  • парсеров и симпатичных принтеров
  • компиляторы
  • синхронизация обновления структур данных
  • базы данных и схемы

и во многих других ситуациях, когда у вас есть модель структуры данных мира и редактируемый вид на эти данные.

Ответ 3

В качестве дополнительной заметки часто упускается из виду, что объективы реализуют очень общее понятие "доступ и обновление поля". Линзы могут быть написаны для всех видов вещей, в том числе объектов, подобных функциям. Для этого требуется немного абстрактного мышления, поэтому позвольте мне показать вам пример мощности линз:

at :: (Eq a) => a -> Lens (a -> b) b

Используя at, вы можете получить доступ к функциям с несколькими аргументами и управлять ими в зависимости от предыдущих аргументов. Просто имейте в виду, что Lens - категория. Это очень полезная идиома для локальных настроек или других вещей.

Вы также можете получить доступ к данным по свойствам или альтернативным представлениям:

polar :: (Floating a, RealFloat a) => Lens (Complex a) (a, a)
mag   :: (RealFloat a) => Lens (Complex a) a

Вы можете пойти дальше для записи линз, чтобы получить доступ к отдельным полосам сигнала, преобразованного в Фурье, и многое другое.