Зачем нам Control.Lens.Reified? Есть ли причина, по которой я не могу разместить Lens
непосредственно в контейнере? Что означает reify
?
Зачем нам нужен Control.Lens.Reified?
Ответ 1
Нам нужны оверенные линзы, потому что система типа Haskell является предикативной. Я не знаю технических подробностей о том, что это значит, но он запрещает такие типы, как
[Lens s t a b]
В некоторых целях приемлемо использовать
Functor f => [(a -> f b) -> s -> f t]
вместо этого, но когда вы достигнете этого, вы не получите Lens
; вы получаете LensLike
, специализирующийся на каком-то функторе или другом. ReifiedBlah
newtypes позволяют вам поддерживать полный полиморфизм.
Оперативно, [ReifiedLens s t a b]
- это список функций, каждый из которых принимает словарь Functor f
, а forall f . Functor f => [LensLike f s t a b]
- это функция, которая принимает словарь Functor f
и возвращает список.
Что касается того, что означает "reify", то словарь скажет что-то, и это, кажется, переводится в довольно ошеломляющее разнообразие конкретных значений в Haskell. Так что не комментируйте это.
Ответ 2
Проблема заключается в том, что в Haskell тип абстракции и приложения полностью неявные; компилятор должен вставить их там, где это необходимо. Различные попытки создания "экспрессивных" расширений, когда компилятор сделал бы умные догадки, где их поставить, потерпели неудачу; поэтому самая безопасная вещь в конечном итоге опирается на правила Haskell 98:
- Абстракции типа отображаются только на верхнем уровне определения функции.
- Приложения типа появляются немедленно, когда в выражении используется переменная с полиморфным типом.
Итак, если я определяю простой объектив: [1]
lensHead f [] = pure []
lensHead f (x:xn) = (:xn) <$> f x
и использовать его в выражении:
[lensHead]
lensHead
автоматически применяется к некоторому набору параметров типа; после чего он больше не является объективом, потому что он больше не является полиморфным в функторе. Вывод: выражение всегда имеет некоторый мономорфный тип; так что это не объектив. (Вы заметите, что функции lens
принимают аргументы типа Getter
и Setter
, которые являются мономорфными типами, по тем же причинам. Но [Getter s a]
не является списком линз, были специализированы только для геттеров.)
Что означает reify
? Определение словаря "сделать реальным". "Обоснование" используется в философии, чтобы ссылаться на акт рассмотрения или рассмотрения чего-либо как реального (а не идеального или абстрактного). В программировании он имеет тенденцию ссылаться на то, что обычно не может рассматриваться как структура данных и представляет его как единое целое. Например, в действительно старых Lisps не использовались первоклассные функции; вместо этого вам пришлось использовать S-выражения для передачи "функций" и eval
их, когда вам нужно было вызвать функцию. S-выражения представляли функции таким образом, что вы могли бы манипулировать в программе, которая называется reification.
В Haskell нам обычно не нужны такие сложные стратегии reification, как Lisp S-выражения, отчасти потому, что язык разработан, чтобы избежать необходимости в них; но поскольку
newtype ReifiedLens s t a b = ReifiedLens (Lens s t a b)
имеет тот же эффект, что и полиморфное значение и превращает его в истинное первоклассное значение, оно упоминается как reification.
Почему это работает, если выражения всегда имеют мономорфные типы? Хорошо, потому что расширение Rank2Types добавляет третье правило:
- Абстракции типа встречаются на верхнем уровне аргументов для определенных функций с так называемыми типами ранга 2.
ReifiedLens
является такой функцией ранга-2; поэтому, когда вы говорите
ReifiedLens l
вы получаете тип лямбда вокруг аргумента ReifiedLens
, а затем l
немедленно применяется к аргументу типа lambda. Итак, l
эффективно просто расширяется. (Компиляторы свободны для eta-уменьшить это и просто использовать l
напрямую).
Затем, когда вы скажете
f (ReifiedLens l) = ...
в правой части, l
- это переменная с полиморфным типом, поэтому каждое использование l
немедленно неявно присваивается любым аргументам типа, необходимым для выражения для проверки типа. Итак, все работает так, как вы ожидаете.
Другим способом думать является то, что если вы скажете
newtype ReifiedLens s t a b = ReifiedLens { unReify :: Lens s t a b }
две функции ReifiedLens
и unReify
действуют как явные операции абстракции типа и приложения; это позволяет компилятору определить, где вы хотите, чтобы абстракции и приложения проходили достаточно хорошо, чтобы проблемы с системами нечистоплотного типа не возникали.
[1] В терминологии lens
это, по-видимому, называется чем-то иным, чем "линзой"; все мое знание линз происходит из презентации SPJ на них, поэтому я не могу проверить это. Точка остается, так как полиморфизм по-прежнему необходим, чтобы заставить его работать как геттер, так и сеттер.