Могу ли я составить список успехов с кратковременным провалом по составу аппликативных функторов?
Пользователь 'singpolyma' спросил reddit, если была какая-то общая структура, лежащая в основе:
data FailList a e = Done | Next a (FailList a e) | Fail e
Была предложена свободная монада, но я подумал, можно ли ее смоделировать в более общем плане с помощью аппликативных функторов. В Abstracting with Applicatives Базерман показывает нам, что сумма двух аппликативных функторов также является аппликативным функтором с смещением влево/вправо, если мы имеют естественное преобразование в направлении смещения. Это похоже на то, что нам нужно! Таким образом, я начал свое предложение, но потом быстро столкнулся с проблемами. Может ли кто-нибудь увидеть решения этих проблем?:
Во-первых, мы начнем с определения суммы двух функторов. Я начал здесь, потому что мы хотим моделировать типы сумм - либо успехи, либо успехи, и неудачу.
data Sum f g a = InL (f a) | InR (g a)
И два функтора, с которыми мы хотим работать:
data Success a = Success [a]
data Failure e a = Failure e [a]
Success
прямолинейный - по существу Const [a]
. Однако Failure e
я не уверен. Это не аппликативный функтор, потому что pure
не имеет никакого определения. Это, однако, экземпляр Apply:
instance Functor Success where
fmap f (Success a) = Success a
instance Functor (Failure e) where
fmap f (Failure e a) = Failure e a
instance Apply (Failure e) where
(Failure e a) <.> (Failure _ b) = Failure e a
instance Apply Success where
(Success a) <.> (Success b) = Success (a <> b)
instance Applicative Success where
pure = const (Success [])
a <*> b = a <.> b
Затем мы можем определить сумму этих функторов с естественным преобразованием справа налево (поэтому левое смещение):
instance (Apply f, Apply g, Applicative g, Natural g f) => Applicative (Sum f g) where
pure x = InR $ pure x
(InL f) <*> (InL x) = InL (f <*> x)
(InR g) <*> (InR y) = InR (g <*> y)
(InL f) <*> (InR x) = InL (f <.> eta x)
(InR g) <*> (InL x) = InL (eta g <.> x)
И единственное, что нам теперь нужно сделать, это определить наше естественное преобразование, и именно здесь все рушится.
instance Natural Success (Failure e) where
eta (Success a) = Failure ???? a
Невозможность создания Failure
кажется проблемой. Кроме того, даже быть взломанным и использовать ⊥ не является вариантом, потому что это будет оценено, в случае, если у вас есть InR (Success ...) <*> InL (Failure ...)
.
Я чувствую, что у меня что-то не хватает, но я понятия не имею, что это такое.
Можно ли это сделать?
Ответы
Ответ 1
Я уверен, что "правильный" ответ состоит в том, чтобы сделать e
моноидом, так как вам не понравилась идея обсуждения reddit.
Рассмотрим Failure "oops" [(*1),(*2),(*3)] <*> Failure "doh" [1,2,3]
Должен ли результат "oops" или "doh" как сбой? Сделав e
моноид, мы зафиксируем тот факт, что канонического выбора нет, и потребитель может выбрать свой яд (будь то First
, Last
, []
и т.д.)
Обратите внимание, что это решение, так же как и представление (Maybe e, [a])
, неверно обрабатывает потоковые/потенциально-бесконечные данные, поскольку оно строго в том, имеет ли мы отказ, заканчивающийся списком.
В другой кодировке будут использоваться фиксированные точки аппликаций в соответствии с последующим сообщением (http://comonad.com/reader/2013/algebras-of-applicatives/).
Затем вы берете представленное там представление списка (FixF (ProductF Embed (Sum (Const ()))) a
) и изменяете его, вставляя моноида ошибки в позицию устройства, чтобы получить следующее:
Monid mon => FixF (ProductF Embed (Sum (Const mon))) a
И обратите внимание, что вместо моноида вы можете использовать Maybe
, чтобы получить ровно FailList
, но, как и в случае с FailList
, вы не получаете аппликативный экземпляр бесплатно, если только вы не пишете, указав право способ совместить ошибки.
Также обратите внимание, что при использовании фиксированного подхода, если у нас есть эквивалент Success [(*1),(*2),(*3)] <*> Failure "doh" [1,2,3,4,5]
, мы возвращаем a Success
с тремя элементами (т.е. мы действительно нестримны в неудаче), в то время как в предложенном вами подходе мы возвращаем Failure
с тремя элементами и ошибкой из списка с пятью списками. Таковы компромиссы потоковой передачи по сравнению с строгими.
Наконец, и наиболее просто, мы можем просто взять type FailList e = Product (Const (First e)) ZipList
, чтобы использовать стандартные аппликативные механизмы и получить что-то очень близкое к исходному типу данных.
Ответ 2
{-# LANGUAGE FlexibleInstances #-}
instance Applicative (Sum Success (Failure e)) where
pure x = InL $ pure x
(InL f) <*> (InL x) = InL (f <*> x)
(InR (Failure e fs)) <*> (InR (Failure _ gs)) = InR (Failure e (fs <*> gs))
(InR (Failure e fs)) <*> (InL (Success gs)) = InR (Failure e (fs <*> gs))
(InL (Success gs)) <*> (InR (Failure e fs)) = InR (Failure e (gs <*> fs))
Это потому, что вы всегда можете добавить сбой в список успехов;)
Вы также можете использовать этот тип класса вместо Natural f g
:
class Transplant f g where
transplant :: f a -> g b -> f b
instance Transplant (Failure e) Success where
transplant (Failure e _) (Success a) = Failure e a
Не знаю, что значит категория-теория.