Выход из монады IO внутри монады продолжения - PullRequest
9 голосов
/ 06 июля 2011

Непонятное название для запутанного вопроса! Я понимаю: а) монады, б) монаду ввода-вывода, в) монаду Cont ( Control.Monad.Cont ) и г) монаду трансформатора продолжения 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

Я делаю хочу весь мой код в стиле продолжения, но я не хочу использовать монаду ввода-вывода для функций, которые не требуют этого. По сути, я хотел бы определить 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?

Ответы [ 3 ]

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

Здесь 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, даже если этот пример этого не делает.

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

5 голосов
/ 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.(Вероятно, производительность хуже.)

1 голос
/ 19 июня 2014

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

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

...