Модификация внутреннего считывателя в стеке трансформатора
Я собираю код из разных мест и пытаюсь разобраться со следующим:
проблема
У меня есть стек трансформаторов со следующим упрощенным типом:
action :: m (ReaderT r IO) a
и я пытаюсь использовать действие в контексте другого стека, который имеет другую среду чтения:
desired :: m (ReaderT r' IO) a
Я могу конечно предоставить
f :: r' -> r
пример
things :: m (ReaderT r' IO) ()
things = do
-- ... some stuff
-- <want to use action here>
action :: m (ReaderT r IO) a -- broken
-- ... more stuff
pure ()
Что я учел
withReaderT :: (r' -> r) -> ReaderT r m a -> ReaderT r' m a
Проблема в том, что ReaderT является внешней монадой, а я хочу использовать ее для внутренней.
Я также считал, что это может быть связано с MonadBase или MonadTransControl, но я не знаком с их работой.
1 ответ
Я не думаю, что можно написать функцию с подписью:
changeReaderT :: (MonadTrans m)
=> (r -> r')
-> m (ReaderT r IO) a
-> m (ReaderT r' IO) a
вопрос в том, что единственная возможная операция, как правило, по второму аргументу - это t (m (ReaderT r IO)) a
для какого-то монадного трансформатора t
, который ничего не покупает.
Это MonadTrans m
одно ограничение не обеспечивает достаточной структуры, чтобы делать то, что вы хотите. Вам либо нужно m
быть экземпляром типа классов MFunctor
в mmorph
пакет, который позволяет вам изменять внутренний слой стека монад в общем виде, предоставляя такую функцию:
hoist :: Monad m => (forall a. m a -> n a) -> t m b -> t n b
(это то, о чем говорил @Juan Pablo Santos), или же вам нужна способность копаться в структуре вашего m
Монадный трансформатор, чтобы частично запустить и восстановить его (который будет специфичным для трансформатора).
Первый подход (с использованием hoist
от mmorph
пакет) будет наиболее удобным, если ваш m
уже состоит из трансформаторов, поддерживаемых mmorph
пакет. Например, следующие проверки типов, и вам не нужно писать никаких экземпляров:
type M n = MaybeT (StateT String n)
action :: M (ReaderT Double IO) a
action = undefined
f :: Int -> Double
f = fromIntegral
desired :: M (ReaderT Int IO) a
desired = (hoist $ hoist $ withReaderT fromIntegral) action
Вам понадобится hoist
для каждого слоя в M
,
Второй подход избегает hoist
и реквизит MFunctor
случаи, но требует адаптации к вашему конкретному M
, Для приведенного выше типа это выглядит примерно так:
desired' :: M (ReaderT Int IO) a
desired' = MaybeT $ StateT $ \s ->
(withReaderT fromIntegral . flip runStateT s . runMaybeT) action
Вам в основном нужно запустить монаду до ReaderT
слой, а затем восстановить его обратно, обрабатывая слои как StateT
с осторожностью. Это именно то, что MFunctor
случаи в mmorph
делают автоматически.