Почему функция набора призмы не возвращает Option/Maybe
В функциональной оптике, призма с хорошим поведением (я полагаю, называется частичной линзой в скале) должна иметь заданную функцию типа 'subpart -> 'parent -> 'parent
где, если призма "успешна" и структурно совместима с 'parent
аргумент дан, то он возвращает 'parent
дано с соответствующим измененным подразделом, чтобы иметь 'subpart
значение дано. Если призма "терпит неудачу" и конструктивно несовместима с 'parent
аргумент, то он возвращает 'parent
дано без изменений.
Мне интересно, почему призма не возвращает 'parent option
(Maybe
для Haskellers), чтобы представить природу / неудачу установленной функции? Разве программист не может сказать по типу возвращаемого значения, был ли набор "успешным" или нет?
Я знаю, что было проведено много исследований и размышлений в области функциональной оптики, поэтому я уверен, что должен быть окончательный ответ, который я просто не могу найти.
(Я из F# фона, поэтому я прошу прощения, если синтаксис, который я использовал, немного непрозрачен для программистов на Haskell или Scala).
2 ответа
Чтобы ответить на вопрос "почему" - линзы и т. Д. Довольно жестко выведены из теории категорий, так что это на самом деле довольно четко - описываемое вами поведение просто выпадает из математики, это не то, что кто-то определил для какой-либо цели, но следует из далекого более общие идеи.
Хорошо, это не очень приятно.
Не уверен, что системы типов других языков достаточно мощны, чтобы выразить это, но в принципе и в Haskell призма является частным случаем обхода. Обход - это способ "посетить" все вхождения "элементов" в некотором "контейнере". Классический пример
mapM :: Monad m => (a -> m b) -> [a] -> m [b]
Это обычно используется как
Prelude> mapM print [1..4]
1
2
3
4
[(),(),(),()]
Основное внимание здесь уделяется последовательности действий / побочных эффектов и получению результата в контейнере с той же структурой, с которой мы начали.
Что особенного в призме, так это то, что контейнеры ограничены одним или нулевым элементом† (тогда как общий обход может проходить через любое количество элементов). Но set
Оператор не знает об этом, потому что это строго более общее. Приятно то, что вы можете использовать это на объективе, призме или mapM
и всегда получаю разумное поведение. Но дело не в том, чтобы "вставить ровно один раз в структуру или сказать, не удалось ли это".
Не то чтобы это не разумная операция, просто это не то, что библиотеки линз называют "настройкой". Вы можете сделать это, явно сопоставив и перестроив:
set₁ :: Prism s a -> a -> s -> Maybe s
set₁ p x = case matching p x of
Left _ -> Nothing
Right a -> Just $ a ^. re p
†Точнее, призма разделяет случаи: контейнер может содержать либо один элемент, и ничего кроме него, либо он может не иметь элемента, но возможно что-то не связанное.
Я сомневаюсь, что есть один окончательный ответ, поэтому я дам вам два здесь.
происхождения
Я полагаю, что призмы были впервые представлены (Дэн Доэль, если моё смутное воспоминание верно) как "линзы". В то время как объектив от s
в a
предложения
get :: s -> a
set :: (s, a) -> s
призма из s
в a
предложения
coget :: a -> s
coset :: s -> Either s a
Все стрелки перевернуты, и произведение, (,)
, заменен на побочный продукт, Either
, Таким образом, призма в категории типов и функций является линзой в двойной категории.
Для простых призм это s -> Either s a
кажется немного странным Почему вы хотите вернуть первоначальное значение? Но lens
Пакет также предлагает сменную оптику. Таким образом, мы в конечном итоге
get :: s -> a
set :: (s, b) -> t
coget :: a -> s
coset :: t -> Either s b
Внезапно то, что мы получаем в случае несоответствия, может на самом деле быть немного другим! О чем это? Вот пример:
cogetLeft :: a -> Either a x
cogetLeft = Left
cosetLeft :: Either b x -> Either (Either a x) b
cosetLeft (Left b) = Right b
cosetLeft (Right x) = Left (Right x)
Во втором (не совпадающем) случае возвращаемое нами значение совпадает, но его тип был изменен.
Хорошая иерархия
Для Ван Ларховена (как в lens
) и рамки стиля профессора, и линзы и призмы могут также заменять обходы. Чтобы сделать это, они должны иметь похожие формы, и этот дизайн выполняет это. Ответ leftaroundabout дает более подробную информацию по этому аспекту.