2011-07-06 4 views
9

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

Но мне интересно, как я мог бы написать программу, где некоторые функции в сочетании прод/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 

Я делать хочу весь мой код в стиле продолжения, но я Дон» t хотят использовать монаду 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?

ответ

17

Это где 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.

+1

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

5

Я обнаружил, что это делает именно то, что я хотел (без изменения 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. (Это, вероятно, имеет худшую производительность, хотя.)

Смежные вопросы