mapMonadTrans :: MonadTrans xT => (ma -> nb) -> xT ma -> xT nb - PullRequest
5 голосов
/ 06 июля 2011

Проблема заключается в следующем. У меня есть:

f :: MonadIO m => ReaderT FooBar m Answer;
f = (liftIO getArgs) >>= ...

Мне нужно запустить это с измененными аргументами. Однако, поскольку m неизвестно, я не могу просто использовать

mapReaderT (withArgs args) :: ReaderT r IO b -> ReaderT r IO b

так как мне нужно как-то преобразовать (с args args) в m для всех m.

Одна найденная мною возможность - определить свою собственную с помощью ArArgs, таким образом:

import System.Environment (setArgs, freeArgv);
withArgv new_args act = do {
  pName <- liftIO System.Environment.getProgName;
  existing_args <- liftIO System.Environment.getArgs;
  bracket (liftIO $ setArgs new_args)
          (\argv -> do {
                      _ <- liftIO $ setArgs (pName:existing_args);
                      liftIO $ freeArgv argv;
                    })
          (const act);
};

withArgs xs act = do {
  p <- liftIO System.Environment.getProgName;
  withArgv (p:xs) act;
};

Однако, это клудж, и относится только к одной функции - мне нужно было бы переписывать каждый withX :: X -> IO a -> IO a, например, Control.Exception.handle

Что, если таковой имеется, лучший способ сделать это?

Редактировать: В случае с дескриптором я обнаружил Control.Monad.CatchIO. В другом случае я использовал еще один, более короткий кладж (не стоит размещать), чтобы избежать вышеупомянутого кладжа. Все еще ищем лучшее решение!

Ответы [ 4 ]

8 голосов
/ 07 июля 2011

Часть того, что вы ищете, - это поднятие гомоморфизма монад в преобразователь монад.

class MonadHoist t where
    hoist :: (Monad m, Monad n) => (forall a. m a -> n a) -> t m a -> t n a

    t :: Monad m => t Identity a -> t m a
    t = hoist (return . runIdentity)

То есть, учитывая гомоморфизм монад f от m до n, вы можете получить гомоморфизм монад от t m до t n, используя подъемник.

Гомоморфизм монад немного сильнее, чем приведенные выше типы, а именно он отвечает за сохранение законов монады.

f . return = return
f . fmap g = fmap g . f
f . join = join . f . fmap f
         = join . fmap f . f -- by the second law
         = (>>= f) . f       -- >>= in terms of join

Обратите внимание на квантификатор, который я выбрал в типе hoist, MonadHoist оказывается, что эта гибкость нужна почти для всех случаев!(Reader оказывается единственным случаем, когда этого не происходит. Попробуйте написать MaybeT без него.)

В общем случае монадные преобразователи могут создавать этот класс.Например:

instance MonadHoist (StateT s) where
    hoist f (StateT m) = StateT (f . m)

instance MonadHoist (ReaderT e) where
    hoist f (ReaderT m) = ReaderT (f . m)

instance MonadHoist MaybeT where
    hoist f (MaybeT m) = MaybeT (f m)

В настоящее время мы не предоставляем его в пакете transformers или mtl, потому что для этого потребуется Rank2Type, но это довольно просто реализовать.

Если на него будет достаточно спроса, я с радостью упакую его в пакет monad-extras.

Теперь, я сказал часть, потому что пока он отвечает на вопрос, заданный типом в теме вашегопост, это не удовлетворяет потребности, отраженной в объеме текста, связанного с вашим вопросом!

Для этого вы, вероятно, захотите последовать совету Луки.=)

4 голосов
/ 07 июля 2011

Пакет monad-control сделает это. Я думаю, что вам нужна функция liftIOOp_ из Control.Monad.IO.Control .

В частности,

liftIOOp_ (withArgs newArgs) f

должен делать то, что вы хотите. С помощью функции liftIOOp вы можете поднимать такие вещи, как bracket.

4 голосов
/ 06 июля 2011

Я полагаю, что пакет interleavableIO решает эту проблему.Это обсуждается в этой ветке кафе .

0 голосов
/ 07 июля 2011

Кажется, вы также можете использовать runReaderT, чтобы получить желаемый эффект:

*> :t withArgs [] (runReaderT f FooBar)
withArgs [] (runReaderT f FooBar) :: IO Answer

, где FooBar - это какой-то конструктор данных, а f определено как указано выше.

...