Модификация внутреннего считывателя в стеке трансформатора

Я собираю код из разных мест и пытаюсь разобраться со следующим:

проблема

У меня есть стек трансформаторов со следующим упрощенным типом:

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 делают автоматически.

Другие вопросы по тегам