Выход из монады IO внутри монады Continuation

Запутанный заголовок для запутанного вопроса! Я понимаю а) монады, б) монаду IO, в) монаду Cont (Control.Monad.Cont) и d) ContT монада-преобразователь продолжения. (И я смутно понимаю преобразователи монад в целом — хотя недостаточно, чтобы ответить на этот вопрос.) Я понимаю, как написать программу, в которой все функции находятся в монаде Cont (Cont r a), и я понимаю как написать программу, в которой все функции находятся в объединенной монаде Cont/IO (ContT r IO a).

Но мне интересно, как мне написать программу, в которой некоторые функции находятся в комбинированной монаде Cont/IO (ContT r IO a), а другие функции находятся только в монаде Cont (Cont r a ). По сути, я хочу написать всю программу в стиле продолжения, но использовать монаду IO только там, где это необходимо (как и в «обычном» коде Haskell, я использую монаду IO только там, где это необходимо).

Например, рассмотрим эти две функции в стиле без продолжения:

foo :: Int -> IO Int
foo n = do
    let x = n + 1
    print x
    return $ bar x

bar :: Int -> Int
bar m = m * 2

Обратите внимание, что foo требует ввода-вывода, но bar является чистым. Теперь я понял, как написать этот код полностью, используя монаду-продолжение, но мне нужно было также пропустить IO через bar:

foo :: Int -> ContT r IO Int
foo n = do
    let x = n + 1
    liftIO $ print x
    bar x

bar :: Int -> ContT r IO Int
bar m = return $ m * 2

Я действительно хочу, чтобы весь мой код был в стиле продолжения, но я не хочу использовать монаду IO для функций, которые в ней не нуждаются. По сути, я хотел бы определить bar следующим образом:

bar :: Int -> Cont r Int
bar m = return $ m * 2

К сожалению, я не могу найти способ вызвать Cont r a монадную функцию (bar) из ContT r IO a монадной функции (foo). Есть ли способ «поднять» непреобразованную монаду в преобразованную? т. е. как изменить строку "bar x" в foo, чтобы она могла корректно вызывать bar :: Int -> Cont r Int?


person mgiuca    schedule 06.07.2011    source источник


Ответы (3)


Здесь Control. Входит Monad.Class. Сделайте bar полиморфным в том, в какой монаде он может работать:

bar :: MonadCont m => Int -> m Int
bar m = return $ m * 2

Обратите внимание, что список экземпляров в нижней части страницы показывает, что экземпляры MonadCont, известные на момент создания документов, включают как Cont r, так и Monad m => ContT r m. Кроме того, класс MonadCont определяет функцию callCC, которая необходима для использования функций продолжения. Это означает, что вы можете использовать всю выразительность продолжений в bar, даже если в этом примере это не так.

Таким образом, вы пишете функции, которые явно не могут использовать ввод-вывод, потому что они не имеют ограничения MonadIO, а их тип явно не упоминает IO. Но они полиморфны в том, в какой монаде они работают, так что их можно тривиально вызывать из контекстов, которые включают IO.

person Carl    schedule 06.07.2011
comment
Спасибо. Это работает. Я также нашел свое собственное решение, которое дало мне именно то, что я хотел (мне не пришлось менять Bar): liftCont :: Cont (m r) a -> ContT r m a; liftCont c = ContT $ runCont c. Мое решение распаковывает файл Cont и создает файл ContT. Я думаю, что ваше решение лучше, потому что оно полиморфно и не требует реальных манипуляций со структурами данных, так что отметьте для себя. Но я опубликую свой как еще один ответ, так как это полезно, если вы не можете изменить bar. Также +1 за объяснение, почему было бы невозможно использовать IO в bar. - person mgiuca; 06.07.2011

Я обнаружил, что это делает именно то, что я хотел (без изменения Bar):

liftCont :: Cont (m r) a -> ContT r m a
liftCont = ContT . runCont

Это распаковывает Cont и создает ContT.

Затем я могу использовать liftCont для вызова Bar из Foo:

foo n = do
    let x = n + 1
    liftIO $ print x
    liftCont $ bar x

Я не думаю, что это "лучше", чем решение Карла (я поставил ему галочку), но я разместил его здесь, потому что оно позволяет вам использовать Bar без изменения его типа, что очень полезно, если вы не можете изменить Bar. (Хотя у него, вероятно, худшая производительность.)

person mgiuca    schedule 07.07.2011

Другой вариант — рассмотреть пакет mmorph https://hackage.haskell.org/package/mmorph-1.0.0/docs/Control-Monad-Morph.html#v%3ahoist

В разделе учебника посмотрите, на что способен hoist.

person Andrew Thaddeus Martin    schedule 19.06.2014