Я читал эту статью для понимания линз. Я знаю, что это отличается от Комплект линз Эдварда Кнетта, но, тем не менее, он полезен для основ.

Итак, линза определяется так:

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

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

(.) :: Lens y z -> Lens x y -> Lens x z
id :: Lens x x

И после этого я просто смотрю на него весь день. Каков именно процесс мысли при написании его определения?

3
Sibi 14 Мар 2014 в 16:26

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 для себя, используя дырки в типах и при необходимости обманывая их.

4
Markus1189 14 Мар 2014 в 17:34
Спасибо, но мне все еще интересно узнать, как определить эти функции.
 – 
Sibi
14 Мар 2014 в 16:42
Спасибо, шрифтовые отверстия кажутся довольно удивительными.
 – 
Sibi
14 Мар 2014 в 17:47

Попробуй это:

(.) :: 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 = ...
1
chi 14 Мар 2014 в 16:42

Кажется, это довольно простой процесс. Но также необходимо проверить, что вы получили категорию - это требует эквационального рассуждения - потому что, например, есть по крайней мере еще один способ реализовать установщик 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)
1
Sassa NF 15 Мар 2014 в 01:08