Почему функция набора призм не возвращает вариант/может быть

В функциональной оптике хорошо работающая призма (я полагаю, называемая частичной линзой в scala) должна иметь функцию set типа 'subpart -> 'parent -> 'parent, где, если призма «успешна» и структурно совместима с заданный аргумент 'parent, затем он возвращает заданный 'parent с соответствующей измененной подчастью, чтобы иметь заданное значение 'subpart. Если призма "проваливается" и структурно несовместима с аргументом 'parent, то она возвращает заданное 'parent без изменений.
Мне интересно, почему призма не возвращает 'parent option (Maybe для Haskellers) для представления прохождения/непрохождения характер заданной функции? Разве программист не должен знать по возвращаемому типу, был ли набор «успешным» или нет?

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

(Я работаю с F#, поэтому прошу прощения, если синтаксис, который я использовал, немного непонятен для программистов на Haskell или Scala).


person Nathan Wilson    schedule 23.09.2017    source источник


Ответы (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

Точнее: призма разделяет случаи: контейнер может либо содержать один элемент и ничего кроме него, либо в нем может не быть никакого элемента, но, возможно, что-то несвязанное.

person leftaroundabout    schedule 23.09.2017
comment
Это Just на самом деле Right? - person Daniel Wagner; 24.09.2017
comment
Спасибо, кажется, теперь я понимаю. Возможно, я напишу свою собственную версию операции множества. F# (в котором на данный момент отсутствуют типы более высокого порядка) не позволил бы мне выразить общую форму обхода, поэтому тот факт, что призма является частным случаем обхода, на самом деле не имеет особой пользы, насколько я знаю. из. - person Nathan Wilson; 25.09.2017
comment
@NathanWilson, я подозреваю, что призмы с изменением типа могут быть полезны даже в этом ограниченном контексте, и им действительно нужна дополнительная гибкость Either. - person dfeuer; 25.09.2017
comment
@dfeuer, честно говоря, я никогда не слышал о призмах, меняющих тип, пока не прочитал ваш ответ. Конечно, мне придется провести некоторое исследование - спасибо, что указали мне на это. - person Nathan Wilson; 25.09.2017
comment
@NathanWilson, основная идея состоит в том, что если значение не создается из указанного конструктора, то любые параметры, упомянутые только в указанном конструкторе, не имеют значения и могут быть изменился на что угодно. [] :: forall a. [a], Nothing :: forall a. Maybe a, Left (x :: a) :: forall b. Either a b, Right (x :: b) :: forall a. Either a b и т. д. - person dfeuer; 25.09.2017

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

Источник

Я полагаю, что призмы были впервые представлены (если мои смутные воспоминания верны, Дэном Доэлом) как «колинзы». В то время как объектив от 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 дает более подробную информацию об этом аспекте.

person dfeuer    schedule 23.09.2017