Ответ 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.