Сопутствующие функторы определяют монадные трансформаторы, но где их поднять?
Я заинтригован описанной здесь конструкцией для определения монадного преобразователя из сопряженных функторов. Вот некоторый код, который обобщает основную идею:
{-# LANGUAGE MultiParamTypeClasses #-}
import Control.Monad
newtype Three g f m a = Three { getThree :: g (m (f a)) }
class (Functor f, Functor g) => Adjoint f g where
counit :: f (g a) -> a
unit :: a -> g (f a)
instance (Adjoint f g, Monad m) => Monad (Three g f m) where
return = Three . fmap return . unit
m >>= f = Three $ fmap (>>= counit . fmap (getThree . f)) (getThree m)
instance (Adjoint f g, Monad m) => Applicative (Three g f m) where
pure = return
(<*>) = ap
instance (Adjoint f g, Monad m) => Functor (Three g f m) where
fmap = (<*>) . pure
При условии Adjoint ((,) s) ((->) s)
, Three ((->) s) ((,) s)
кажется эквивалентным StateT s
,
Очень круто, но я озадачен парой вещей:
Как мы можем обновить монадический
m a
в монадическийThree g f m a
? Для конкретного случаяThree ((->) s) ((,) s)
Конечно, очевидно, как это сделать, но, кажется, желательно иметь рецепт, который работает для любогоThree g f
при условии, чтоAdjoint f g
, Другими словами, похоже, что должен быть аналогlift
чье определение требует толькоunit
,counit
иreturn
а также>>=
входной монады. Но я не могу найти один (я видел определение, используяsequence
, но это похоже на обман, так как это требуетf
бытьTraversable
).В этом отношении, как мы можем обновить
g a
вThree g f m a
(предоставленаAdjoint f g
)? Опять же, для конкретного случаяThree ((->) s) ((,) s)
очевидно, как это сделать, но мне интересно, есть ли аналогgets
это требует толькоunit
,counit
иreturn
а также>>=
входной монады.
2 ответа
lift
В ответе Бенджамина Ходжсона это выглядит так:
lift mx = let mgfx = fmap unit mx gmfx = distributeR mgfx in Three gmfx -- or lift = Three . distributeR . fmap unit
Как вы знаете, это не единственная правдоподобная стратегия, которую мы могли бы использовать там:
lift mx = let gfmx = unit mx
gmfx = fmap sequenceL gfmx
in Three gmfx
-- or
lift = Three . fmap sequenceL . unit
Откуда Traversable
требование для соответствующего Эдварда Кметта MonadTrans
экземпляр происходит. Тогда возникает вопрос: а не полагаться ли на это, как вы выразились, на "обман"? Я собираюсь утверждать, что это не так.
Мы можем адаптировать план игры Бенджамина относительно Distributive
и правые смежные и попытаться найти, являются ли левые смежныеTraversable
, Взгляд наData.Functor.Adjunction
показывает, что у нас есть неплохой набор инструментов для работы:
unabsurdL :: Adjunction f u => f Void -> Void
cozipL :: Adjunction f u => f (Either a b) -> Either (f a) (f b)
splitL :: Adjunction f u => f a -> (a, f ())
unsplitL :: Functor f => a -> f () -> f a
Эдвард услужливо говорит нам, что unabsurdL
а также cozipL
свидетельствуйте, что "[левый сопряженный должен быть заселен, и [что] левый сопряженный должен быть заселен ровно одним элементом", соответственно. Это, однако, означает, splitL
точно соответствует разложению формы и содержания, которое характеризует Traversable
функторы. Если добавить к этому тот факт, что splitL
а также unsplitL
обратные, реализация sequence
следует сразу:
sequenceL :: (Adjunction f u, Functor m) => f (m a) -> m (f a)
sequenceL = (\(mx, fu) -> fmap (\x -> unsplitL x fu) mx) . splitL
(Обратите внимание, что не более Functor
требуется от m
, как и ожидалось для перемещаемых контейнеров, которые содержат ровно одно значение.)
Все, чего не хватает на этом этапе, это проверки того, что обе реализации lift
эквивалентны. Это не сложно, только немного трудоемко. Короче говоря, distributeR
а также sequenceR
Определения здесь могут быть упрощены до:
distributeR = \mgx ->
leftAdjunct (\fa -> fmap (\gx -> rightAdjunct (const gx) fa) mgx) ()
sequenceL =
rightAdjunct (\mx -> leftAdjunct (\fu -> fmap (\x -> fmap (const x) fu) mx) ())
Мы хотим показать, что distributeR . fmap unit = fmap sequenceL . unit
, После нескольких раундов упрощения мы получаем:
distributeR . fmap unit = \mgx ->
leftAdjunct (\fa -> fmap (\gx -> rightAdjunct (const (unit gx)) fa) mgx) ()
fmap sequenceL . unit = \mx ->
leftAdjunct (\fu -> fmap (\x -> fmap (const x) fu) mx) ()
Мы можем показать, что это действительно одно и то же, выбрав \fu -> fmap (\x -> fmap (const x) fu) mx
- аргумент leftAdjunct
во второй правой части - и скольжения rightAdjunct unit = counit . fmap unit = id
внутрь:
\fu -> fmap (\x -> fmap (const x) fu) mx
\fu -> fmap (\x -> fmap (const x) fu) mx
\fu -> fmap (\x -> (counit . fmap unit . fmap (const x)) fu) mx
\fu -> fmap (\x -> rightAdjunct (unit . const x) fu) mx
\fu -> fmap (\x -> rightAdjunct (const (unit x)) fu) mx
-- Sans variable renaming, the same as
-- \fa -> fmap (\gx -> rightAdjunct (const (unit gx)) fa) mgx
Вывод в том, что Traversable
маршрут к вашему MonadTrans
так же безопасен, как и Distributive
один, и опасения по этому поводу - в том числе упомянутые Control.Monad.Trans.Adjoint
документация - больше не должна никого беспокоить.
PS: Стоит отметить, что определение lift
Выдвинутые здесь можно записать как:
lift = Three . leftAdjunct sequenceL
То есть, lift
является sequenceL
отправлено через изоморфизм присоединения. Кроме того, из...
leftAdjunct sequenceL = distributeR . fmap unit
... если мы применим rightAdjunct
с обеих сторон мы получаем...
sequenceL = rightAdjunct (distributeR . fmap unit)
... и если мы сочиняем fmap (fmap counit)
слева от обеих сторон мы в итоге получим:
distributeR = leftAdjunct (fmap counit . sequenceL)
Так distributeR
а также sequenceL
являются определяемыми.
Как мы можем обновить монадический
m a
в монадическийThree g f m a
?
Хороший вопрос. Время для игры в теннис типа!
-- i'm using Adjuction from the adjunctions package because I'll need the fundeps soon
lift :: Adjunction f g => m a -> Three g f m a
lift mx = Three _
Отверстие набрано g (m (f a))
, У нас есть mx :: m a
по объему и, конечно, unit :: a -> g (f a)
а также fmap :: (a -> b) -> m a -> m b
,
lift mx = let mgfx = fmap unit mx
in Three $ _ mgfx
Теперь это _ :: m (g (f a)) -> g (m (f a))
, Это distribute
если g
является Distributive
,
lift mx = let mgfx = fmap unit mx
gmfx = distributeR mgfx
in Three gmfx
-- or
lift = Three . distributeR . fmap unit
Так что теперь нам просто нужно доказать, что правая часть присоединения всегда Distributive
:
distributeR :: (Functor m, Adjunction f g) => m (g x) -> g (m x)
distributeR mgx = _
Так как нам нужно вернуть g
Четкий выбор методов из Adjunction
является leftAdjunct :: Adjunction f g => (f a -> b) -> a -> g b
, который использует unit
создать g (f a)
а затем сносит внутреннюю f a
от fmap
пинг функции.
distributeR mgx = leftAdjunct (\fa -> _) _
Сначала я собираюсь атаковать первую лунку, ожидая, что ее заполнение может рассказать мне кое-что о второй лунке. Первое отверстие имеет тип m a
, Единственный способ получить m
любого типа является fmap
что-нибудь пропинговать mgx
,
distributeR mgx = leftAdjunct (\fa -> fmap (\gx -> _) mgx) _
Теперь первая дыра имеет тип a
, и у нас есть gx :: g a
в рамках. Если бы у нас был f (g a)
мы могли бы использовать counit
, Но у нас есть f x
(где x
в настоящее время является неопределенной переменной типа) и g a
в рамках.
distributeR mgx = leftAdjunct (\fa -> fmap (\gx -> counit (fa $> gx)) mgx) _
Оказывается, что оставшееся отверстие имеет неоднозначный тип, поэтому мы можем использовать все, что захотим. (Это будет игнорироваться $>
.)
distributeR mgx = leftAdjunct (\fa -> fmap (\gx -> counit (fa $> gx)) mgx) ()
Этот вывод мог показаться волшебным трюком, но на самом деле вы просто лучше тренируетесь в теннисе. Навык игры заключается в умении смотреть на типы и применять интуицию и факты об объектах, с которыми вы работаете. Посмотрев на типы, я мог сказать, что мне нужно будет обменяться m
а также g
и обход m
был не вариант (потому что m
не обязательно Traversable
) так что-то вроде distribute
собирался быть необходимым.
Помимо догадки мне нужно было реализовать distribute
Я руководствовался некоторыми общими знаниями о том, как работают дополнения.
В частности, когда вы говорите о * -> *
единственные интересные дополнения (однозначно изоморфны) Reader
/ Writer
примыкание. В частности, это означает, что любое право присоединяется к Hask
всегда Representable
как свидетельствует tabulateAdjunction
а также indexAdjunction
, Я также знаю, что все Representable
функторы Distributive
(на самом деле логически обратное также верно, как описано в Distributive
документы, даже если классы не эквивалентны по мощности), в соответствии с distributeRep
,
В этом отношении, как мы можем обновить
g a
вThree g f m a
(предоставленаAdjoint f g
)?
Я оставлю это как упражнение. Я подозреваю, что вам нужно опираться на g ~ ((->) s)
опять изоморфизм. Я на самом деле не ожидаю, что это будет верно для всех дополнений, только те, на Hask
из которых есть только один.