11

Я читал A wreq tutorial:Как можно «реализовать» композиционные линзы Хаскелла с использованием функционального состава с этим странным порядком аргументов?

Линза обеспечивает способ сосредоточиться на части стоимости Haskell. Например, для модели тип Response имеет объектив responseStatus, который фокусируется на информации о состоянии, возвращаемой сервером.

ghci> r ^. responseStatus 
Status {statusCode = 200, statusMessage = "OK"} 

^. оператор принимает значение в качестве первого аргумента, линз, как его вторых, и возвращает часть стоимости сосредоточена на линзе.

Мы составляем объективы с использованием функционального состава, который позволяет нам легко сфокусироваться на части глубоко вложенной структуры.

ghci> r ^. responseStatus . statusCode 
200 

Я не могу придумать способ, как функция состава делается с этим порядком аргументов может относиться к вложенности структуры в этом порядке.

Посмотрите: r ^. responseStatus . statusCode может быть r ^. (responseStatus . statusCode) или (r ^. responseStatus) . statusCode.

В первом говорится, что мы построим функцию, которая первых лечит statusCode (получает его из записи Status - как я могу вывести из показываемого значения Status {statusCode = 200, statusMessage = "OK"}), а затем передает его в responseStatus, которые должны лечить статус ответа. Итак, это наоборот: на самом деле код состояния является частью статуса ответа.

Второе чтение также не имеет смысла для меня, потому что оно также относится к коду состояния.

+3

'statusCode' должен быть объективом, а не селектором поля записи. Я думаю, они, должно быть, спрятали селектор поля и экспортировали объектив с тем же именем; довольно запутанным, если вы спросите меня. (Или написал пользовательский экземпляр Show.) –

+0

@ReidBarton Это должно быть частью головоломки. Поскольку я очень мало знаю об объективах, мой вопрос также связан с основной идеей: как нормальная функциональная композиция может использоваться для доступа к структуре в обратном порядке? (Это немного напоминает стиль продолжения прохождения). –

+3

Это по существу правильно. Линзы являются похожими на вычисления CPS, поэтому они, как побочный эффект, включают функцию флип-функции. –

ответ

12

Правильное считывание r ^. responseStatus . statusCode - r ^. (responseStatus . statusCode). Это естественно, так как состав функций возвращает функцию при применении к двум аргументам, поэтому (r ^. responseStatus) . statusCode должен возвращать функцию, а не любое значение, которое может быть распечатано.

Это все еще оставляет открытым вопрос, почему линзы сочиняют в «неправильном» порядке. Поскольку реализация линз немного волшебна, давайте рассмотрим более простой пример.

first это функция, которая отображает над первым элементом пары:

first :: (a -> b) -> (a, c) -> (b, c) 
first f (a, b) = (f a, b) 

Что map . first делать? first принимает функцию, действующую на первый элемент и возвращает функцию, действующая на пару, которая является более очевидной, если мы круглые скобки типа так:

first :: (a -> b) -> ((a, c) -> (b, c)) 

Кроме того, напомним, тип map:

map :: (a -> b) -> ([a] -> [b]) 

map выполняет функцию, действующую на элемент, и возвращает функцию, действующую на список. Теперь f . g работает, сначала применяя g, а затем подавая результат на f.Таким образом, map . first принимает функцию, действующую на некоторый тип элемента, преобразует ее в функцию, действующую на пары, а затем преобразует ее в функцию, действующую на списки пар.

(map . first) :: (a -> b) -> [(a, c)] -> [(b, c)] 

first и map как включить функции, действующие на части структуры с функциями, действующих на всей структуре. В map . first, какая структура составляет: first делается фокус для map.

(map . first) (+10) [(0, 2), (3, 4)] == [(10, 2), (13, 4)] 

Теперь посмотрим на тип линз:

type Lens = forall f. Functor f => (a -> f b) -> (s -> f t) 

Попытка игнорировать Functor биты на данный момент. Если мы слегка прищурились, это напоминает типы для map и first. И так бывает, что линзы также преобразуют функции, действующие на части структур, в функцию, действующую на целые структуры. В подписи выше s обозначена вся структура, а a обозначает ее часть. Так как наша входная функция может изменить тип a на b (как указано a -> f b), нам также нужен параметр t, что примерно соответствует «типу s после того, как мы изменили на b внутри него».

statusCode представляет собой линзу, которая преобразует функцию, действующую на Int к функции, действующий на Status:

statusCode :: Functor f => (Int -> f Int) -> (Status -> f Status) 

responseStatus преобразует функцию, действующую на Status к функции, действующие на Response:

responseStatus :: Functor f => (Status -> f Status) -> (Response -> f Response) 

Тип responseStatus . statusCode следует по тому же образцу, что и мы, с map . first:

responseStatus . statusCode :: Functor f => (Int -> f Int) -> (Response -> f Response) 

Это еще предстоит выяснить, как именно ^. работы. Он тесно связан с основным механиком и магией линз; Я не буду повторять его здесь, поскольку об этом написано немало работ. Для введения я рекомендую посмотреть на this one и this one, а также вы можете посмотреть this excellent video.

 Смежные вопросы

  • Нет связанных вопросов^_^