привязать монадическое значение (m2 a) внутри некоторой другой монады m1

Сегодня, работая в Coding Dojo, я попробовал следующее.

example :: IO ()
example = do input <- getLine
             parsed <- parseOnly parser input
             ...

где parseOnly :: Parser a -> Either String a (из attoparsec), конечно, компилятор пожаловался, что Either .. не IO .., по сути, говоря мне, что я смешиваю монады.

Конечно, это можно решить

             case parseOnly parser input of .. -> ..

что как-то некрасиво, я думаю. Также я предполагаю, что у кого-то еще была эта проблема ранее, и решение, которое я думаю, связано с преобразователями монад, но последние биты я не могу собрать воедино.

Это также напомнило мне liftIO - но я думаю, что это наоборот, что решает проблему отмены действия ввода-вывода, происходящего внутри некоторой окружающей монады (точнее, MonadIO - скажем, например, внутри Snap, когда кто-то хочет напечатать что-то в stdout при получении какой-то http).

В более общем плане эта проблема кажется для Monad m1 и (другого) Monad m2 как я могу сделать что-то вроде

example = do a <- m1Action
             b <- m2Action
             ..

person epsilonhalbe    schedule 20.01.2016    source источник
comment
Я не думаю, что это монадные трансформеры. IO может находиться только в основе стека преобразователя монад.   -  person dfeuer    schedule 21.01.2016
comment
@dfeuer Вы уверены? ExceptT IO e — вполне разумная монада (построенная из трансформеров), к которой разумно можно поднять как Either e, так и IO действия.   -  person Daniel Wagner    schedule 21.01.2016
comment
@DanielWagner, это хороший момент. IO все еще находится в основании, так что вам придется завернуть все это дело в runExceptT или во что-то еще, но внутри все более удобно. user3237465 превратил это в ответ.   -  person dfeuer    schedule 21.01.2016


Ответы (3)


Нельзя, в общем. Весь блок do должен быть одной определенной монадой (поскольку example должен иметь определенный тип). Если бы вы могли связать произвольную другую монаду внутри этого блока do, у вас было бы unsafePerformIO.

Преобразователи монад позволяют вам создать одну монаду, сочетающую в себе то, что могут делать несколько других монад. Но вы должны решить, что все действия в вашем блоке do используют один и тот же стек преобразователя монад для их использования, это не способ произвольного переключения монад в середине блока do.

Ваше решение с case работает только потому, что у вас есть конкретная известная монада (либо), у которой есть способ извлечения значений из нее. Не все монады обеспечивают это, поэтому невозможно построить общее решение, не зная конкретных задействованных монад. Вот почему синтаксис do block не предоставляет такого сокращения.

person Ben    schedule 21.01.2016

В общем, монадные преобразователи предназначены для такого чередования. Вы можете использовать ExceptT

example :: IO (Either String ())
example = runExceptT $ do
    input <- liftIO getLine
    parsed <- parseOnly parser input
    ...

Обратите внимание, что parseOnly должно возвращать ExceptT String IO a для некоторого a. Или лучше ExceptT String m a по любому m. Или, если вы хотите, чтобы parseOnly возвращало Either String a, это

example :: IO (Either String ())
example = runExceptT $ do
    input <- lift getLine
    parsed <- ExceptT $ return $ parseOnly parser input
    ...

Но я думаю, что все, что вам нужно, это просто

eitherToIO :: Either String a -> IO a
eitherToIO (Left s)  = error s
eitherToIO (Right x) = return x

parseOnly :: ... -> String -> Either String Int

example :: IO ()
example = do
    input  <- getLine
    parsed <- eitherToIO $ parseOnly parser input
    ...
person user3237465    schedule 21.01.2016

Вам нужно сделать это выражение проверкой типа; как в чистом коде. Здесь,

... = do a <- act1  -- m1 monad
         b <- act2  -- m2 monad
         ...

де-сахары до:

... = act1 >>= (\a -> act2 >>= \b -> ...)

>>= стоит подпись:

(>>=) :: Monad m => m a -> (a -> m b) -> m b

внешняя bind специализирована на m1, поэтому ожидается, что выражение внутри скобок будет иметь тип: a -> m1 b, тогда как внутренняя привязка специализирована на m2, поэтому выражение внутри скобок будет иметь тип a -> m2 b:

-- outer bind expects (    \a   ->   m1 b      )
             act1 >>= (\a -> act2 >>= \b -> ...)
-- inner bind results (    \a   ->   m2 b      )

для этого, чтобы проверить тип, вам нужна функция подписи m2 b -> m1 b между двумя; вот что lift< /a> делает для определенного класса монад m2 и m1: а именно m1 ~ t m2, где t является экземпляром MonadTrans:

lift :: (Monad m, MonadTrans t) => m a -> t m a
person behzad.nouri    schedule 21.01.2016