Я читал эту статью для понимания линз. Я знаю, что это отличается от Комплект линз Эдварда Кнетта, но, тем не менее, он полезен для основ.
Итак, линза определяется так:
type Lens a b = (a -> b, b -> a -> a)
Было упомянуто, что линзы образуют категорию, и я пытается создать экземпляр для класса типов Category
. Для начала я написал определение типа для функций:
(.) :: Lens y z -> Lens x y -> Lens x z
id :: Lens x x
И после этого я просто смотрю на него весь день. Каков именно процесс мысли при написании его определения?
3 ответа
Я нашел эту статью («Линзы с нуля на fpcomplete» Джозефа Абрахамсона), будь очень хорошим, он начинается с того же представления линз, с которого вы начали, определяет композицию для него и продолжается по пути к представлению, более похожему на линза
РЕДАКТИРОВАТЬ: Я считаю, что типовые отверстия превосходны при выполнении таких действий:
(<.>):: Lens y z -> Lens x y -> Lens x z
(getA,setA) <.> (getB,setB) = (_,_)
Итак, теперь у нас есть 2 дыры, первая в кортеже говорит (вывод очищен):
Found hole ‘_’ with type: x -> z
...
Relevant bindings include
setB :: y -> x -> x
getB :: x -> y
setA :: z -> y -> y
getA :: y -> z
(<.>) :: Lens y z -> Lens x y -> Lens x z
Присмотревшись к креплениям, то, что нам нужно, у нас уже есть! getB :: x -> y
и getA :: y -> z
вместе с функцией композиции (.) :: (b -> c) -> (a -> b) -> a -> c
Итак, мы с радостью вставляем это:
(<.>):: Lens y z -> Lens x y -> Lens x z
(getA,setA) <.> (getB,setB) = (getA . getB, _)
И продолжите с отверстием второго типа, в котором написано:
Found hole ‘_’ with type: z -> x -> x
Relevant bindings include
setB :: y -> x -> x
getB :: x -> y
setA :: z -> y -> y
getA :: y -> z
Самое похожее, что у нас есть, это setA :: z -> y -> y
, мы начинаем со вставки лямбда-выражения, захватывая аргументы:
(getA,setA) <.> (getB,setB) = (getA . getB, \z x -> _)
Изменение вашего типа отверстие на:
Found hole ‘_’ with type: x
Relevant bindings include
x :: x
z :: z
setB :: y -> x -> x
getB :: x -> y
setA :: z -> y -> y
getA :: y -> z
Мы могли бы вставить x
, который проверяет тип, но не дает нам того, что мы хотим (при установке ничего не происходит). Единственная другая привязка, которая может дать нам x
, - это setB
, поэтому мы вставляем это:
(getA,setA) <.> (getB,setB) = (getA . getB, \z x -> setB _ _)
Наша первая дыра говорит:
Found hole ‘_’ with type: y
Relevant bindings include
x :: x
z :: z
setB :: y -> x -> x
getB :: x -> y
setA :: z -> y -> y
getA :: y -> z
Итак, нам нужен y, глядя на то, что находится в области видимости, getB
может дать нам y
, если мы дадим ему x
, который у нас есть, но это приведет нас к бесполезный объектив снова ничего не делает. Альтернативой является использование setA
:
(getA,setA) <.> (getB,setB) = (getA . getB, \z x -> setB (setA _ _) _)
(С этого момента немного ускорим) И снова первая дыра хочет что-то типа z
, которое у него есть в качестве аргумента нашей лямбда:
(getA,setA) <.> (getB,setB) = (getA . getB, \z x -> setB (setA z _) _)
Чтобы заполнить первое отверстие типа y
, мы можем использовать getB :: x -> y
, передав ему аргумент нашей лямбды:
(getA,setA) <.> (getB,setB) = (getA . getB, \z x -> setB (setA z (getB x)) _)
В результате остается одно оставшееся отверстие типа, которое можно тривиально заменить на x
, что приведет к окончательному определению:
(<.>):: Lens y z -> Lens x y -> Lens x z
(getA,setA) <.> (getB,setB) = (getA . getB, \z x -> setB (setA z (getB x)) x)
Вы можете попробовать определить id
для себя, используя дырки в типах и при необходимости обманывая их.
Попробуй это:
(.) :: Lens y z -> Lens x y -> Lens x z
(getZfromY , setZinY) . (getYfromX , setYinX) = (getZfromX , setZinX)
where getZfromX someX = ...
setZinX someZ someX = ...
Идея такова: объединить два геттера, чтобы создать новый геттер, и объединить два установщика, чтобы создать новый сеттер.
Для идентичности подумайте о:
id :: Lens x x
id = (getXfromX , setXinX)
where getXfromX someX = ...
setXinX newX oldX = ...
Кажется, это довольно простой процесс. Но также необходимо проверить, что вы получили категорию - это требует эквационального рассуждения - потому что, например, есть по крайней мере еще один способ реализовать установщик id
с типом x->x->x
- только один из них сделаю категорию.
Итак, начнем с получения функций нужного типа.
Lens y z -> Lens x y -> Lens x z ==
(y->z, z->y->y) -> (x->y, y->x->x) -> (x->z, z->x->x)
Кажется понятным, как получить x->z
из x->y
и y->z
- составить. Что ж, у вас есть способы построить новый x
из старого x
и нового y
, а также способ получить старый y
из старого x
, так что если вы можете построить новый y
из z
и старый y
, готово.
(.) (yz, zyy) (xy, yxx) = (yz . xy, \z x -> yxx (zyy z (xy x)) x)
Аналогично для id:
Lens x x ==
(x->x, x->x->x)
Так
id = (id, const)
Пока все хорошо, типы проверяют. Теперь давайте проверим, что у нас есть категория. Есть один закон:
f . id = f = id . f
Проверка одним способом (немного неформально, поэтому необходимо иметь в виду, что .
и id
относятся к разным вещам в f . id
и fg . id
):
f . id = (fg, fs) . (id, const) =
(fg . id, \z x -> const (fs z (id x)) x) =
(fg, \z x -> fs z (id x)) = (fg, fs)
Проверяем другим способом:
id . f = (id, const) . (fg, fs) =
(id . fg, \z x -> fs (const z (fg x)) x) =
(fg, \z x -> fs z x) = (fg, fs)
Похожие вопросы
Связанные вопросы
Новые вопросы
haskell
Haskell — это чисто функциональный язык программирования со строгой статической типизацией, отложенными вычислениями, обширной поддержкой параллелизма и параллелизма, а также уникальными возможностями абстракции.