Запутанный заголовок для запутанного вопроса! Я понимаю а) монады, б) монаду 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
?