Ответ 1
Я попробовал немного другой подход, который дает хотя бы частичный ответ. Поскольку штабелирование монад может быть иногда проблематичным, и мы знаем, что все наши монады построены из некоторого типа данных, я попытался вместо этого объединить типы данных.
Мне комфортно с MonadFree
, поэтому я использовал его, но, полагаю, подобный подход можно было бы использовать и для Operational
.
Начнем с определения наших типов данных:
{-# LANGUAGE DeriveFunctor, FlexibleContexts,
FlexibleInstances, FunctionalDependencies #-}
import Control.Monad
import Control.Monad.Free
data SLang x = ReadStr (String -> x) | WriteStr String x
deriving Functor
data ILang x = ReadInt (Int -> x) | WriteInt Int x
deriving Functor
Чтобы объединить два функтора вместе для их использования в свободной монаде, определим их копроизведение:
data EitherF f g a = LeftF (f a) | RightF (g a)
deriving Functor
Если мы создадим свободную монаду над EitherF f g
, мы можем вызвать команды из обоих. Чтобы сделать этот процесс прозрачным, мы можем использовать MPTC, чтобы разрешить преобразование каждого из функторов в целевое:
class Lift f g where
lift :: f a -> g a
instance Lift f f where
lift = id
instance Lift f (EitherF f g) where
lift = LeftF
instance Lift g (EitherF f g) where
lift = RightF
теперь мы можем просто вызвать lift
и преобразовать любую часть в копроизведение.
С помощью вспомогательной функции
wrapLift :: (Functor g, Lift g f, MonadFree f m) => g a -> m a
wrapLift = wrap . lift . fmap return
мы можем, наконец, создать общие функции, которые позволяют нам вызывать команды из всего, что мы можем поднять в функтор:
readStr :: (Lift SLang f, MonadFree f m) => m String
readStr = wrapLift $ ReadStr id
writeStr :: (Lift SLang f, MonadFree f m) => String -> m ()
writeStr x = wrapLift $ WriteStr x ()
readInt :: (Lift ILang f, MonadFree f m) => m Int
readInt = wrapLift $ ReadInt id
writeInt :: (Lift ILang f, MonadFree f m) => Int -> m ()
writeInt x = wrapLift $ WriteInt x ()
Затем программа может быть выражена как
myProgram :: (Lift ILang f, Lift SLang f, MonadFree f m) => m ()
myProgram = do
str <- readStr
writeStr "Length of that str is"
writeInt $ length str
n <- readInt
writeStr "you wanna have it n times; here we go:"
writeStr $ replicate n 'H'
без определения каких-либо дополнительных экземпляров.
Несмотря на то, что все вышеизложенное работает хорошо, проблема заключается в том, как в целом запускать такие состоящие свободные монады. Я не знаю, возможно ли это, чтобы иметь полностью общее, составное решение.
Если у нас есть только один базовый функтор, мы можем запустить его как
runSLang :: Free SLang x -> String -> (String, x)
runSLang = f
where
f (Pure x) s = (s, x)
f (Free (ReadStr g)) s = f (g s) s
f (Free (WriteStr s' x)) _ = f x s'
Если у нас есть два, нам нужно потопить состояние обоих из них:
runBoth :: Free (EitherF SLang ILang) a -> String -> Int -> ((String, Int), a)
runBoth = f
where
f (Pure x) s i = ((s, i), x)
f (Free (LeftF (ReadStr g))) s i = f (g s) s i
f (Free (LeftF (WriteStr s' x))) _ i = f x s' i
f (Free (RightF (ReadInt g))) s i = f (g i) s i
f (Free (RightF (WriteInt i' x))) s _ = f x s i'
Я предполагаю, что одна из возможностей заключается в том, чтобы выразить функторы с помощью iter :: Functor f => (f a -> a) -> Free f a -> a
из free, а затем создать аналогичную функцию объединения
iter2 :: (Functor f, Functor g)
=> (f a -> a) -> (g a -> a) -> Free (EitherF f g) a -> a
Но у меня не было времени попробовать.