Существует ли принципиальный способ создания двух монадных трансформаторов, если они имеют различный тип, но их основная монада одного типа?

Не могу сделать, чтобы расширить вопрос. Но вот прецедент: предположим, у вас есть два монадных трансформатора, t и s, преобразующиеся по одной и той же монаде m:

master :: (MonadTrans t, Monad m) => t m a b
slave :: (MonadTrans t, Monad m) => s m a b

И я хочу составить master и slave, чтобы они могли общаться друг с другом, когда m примитивы были подняты на t и s. Подпись может быть:

bound :: (MonadTrans t, MonadTrans s, Monad m, Monoid a) => t m a b -> s m a b -> (...)
But what is the type of (...) ?

Случай использования в примечании с сахаром:

master :: Monoid a => a -> t m a b
master a = do 
 a <- lift . send $ (a,False) -- * here master is passing function param to slave
 ... -- * do some logic with a
 b <- lift . send $ (mempty,True) -- * master terminates slave, and get back result
slave :: Monoid a => (a -> b) -> s m a b
slave g = do 
 (a,end) <- lift receive
 case end of 
 True -> get >>= \b -> exit b 
 _ -> (modify (++[g a])) >> slave g

Обновление: send и receive являются примитивами типа m.

Я извиняюсь, если этот пример выглядит надуманным или слишком похож на сопрограммы, дух вопроса действительно не имеет к этому никакого отношения, поэтому, пожалуйста, игнорируйте все сходства. Но главное, что монады t и s не могли быть разумно составлены друг с другом раньше, но после того, как обе обернут некоторую базовую монаду m, теперь они могут быть составлены и выполняться как одна функция. Что касается типа скомпонованной функции, я действительно не уверен, поэтому какое-то направление ценится. Теперь, если эта абстракция уже существует, и я просто не знаю об этом, тогда это было бы лучше.

1 ответ

Да. Объедините hoist с пакетом mmorph с lift, чтобы сделать это:

bound
 :: (MonadTrans t, MonadTrans s, MFunctor t, Monad m)
 => t m () -> s m () -> t (s m) ()
bound master slave = do
 hoist lift master
 lift slave

Чтобы понять, почему это работает, изучите тип hoist:

hoist :: (MFunctor t) => (forall x . m x -> n x) -> t m r -> t n r

hoist позволяет изменять базовую монаду любого монадного трансформатора, который реализует MFunctor (который является большинством из них).

Что означает код для bound, это то, что два монадных трансформатора согласуются с конечной целевой монадой, которая в этом случае t (s m). Порядок, в котором вы гнездились t и s, зависит от вас, поэтому я просто предположил, что вы хотите t снаружи.

Тогда это просто вопрос использования различных комбинаций hoist и lift, чтобы получить два подвычисления, чтобы согласовать окончательный стек монады. Первый работает следующим образом:

master :: t m r
hoist lift master :: t (s m) r

Второй работает следующим образом:

slave :: s m r
lift slave :: t (s m) r

Теперь они оба согласны с тем, чтобы мы могли их упорядочить в одном блоке do, и он будет "просто работать".

Чтобы узнать больше о том, как работает hoist, я рекомендую вам проверить документацию для пакета mmorph, который имеет хороший учебник внизу.

licensed under cc by-sa 3.0 with attribution.