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

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

Проблема

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

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, но я не знаком с их работой.


person OllieB    schedule 06.09.2017    source источник
comment
Вы видели hackage.haskell.org/package /mmorph-1.1.0/docs/? Если вы предоставите ReaderT r m a -> ReaderT r' m a, hoist из MFunctor сможет делать то, что вы хотите.   -  person Juan Pablo Santos    schedule 06.09.2017
comment
Сможете ли вы это сделать, также зависит от m. Например, это невозможно с ContT.   -  person Li-yao Xia    schedule 06.09.2017
comment
Верно. Проверьте экземпляры для MFunctor для получения дополнительной информации.   -  person Juan Pablo Santos    schedule 07.09.2017


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

person K. A. Buhr    schedule 06.09.2017