Прыжки вперед с продолжением монады

Можно перепрыгнуть назад в программе с продолжением монады:

{-# LANGUAGE RecursiveDo #-}

import Control.Monad.Fix
import Control.Monad.Trans.Cont

setjmp = callCC (\c -> return (fix c))

backward = do
  l <- setjmp
  -- some code to be repeated forever
  l

Но когда я пытаюсь перейти вперёд, он не принимается GHC:

forward = mdo
  l
  -- some dead code
  l <- setjmp  
  return ()

Это не работает, потому что нет экземпляра для MonadFix (ContT r m) для продолжения monad-трансформатора ContT, определенного в Control.Monad.Trans.Cont. Дополнительную информацию см. В разделе 5.1 тезис Левтера Эркока.

Есть ли способ кодирования прямого перехода без рекурсии значения для продолжения монады?

Есть ли альтернативное определение ContT, которое имеет экземпляр для MonadFix (ContT r m)? Существует неопубликованный проект Магнуса Карлсона, который делает такое предложение, но я не уверен, что с ним делать в моем случае.

Ответ 1

Вы можете сделать это, если вы переместите свой мертвый код внутри callCC, например:

import Control.Monad.Cont

forward :: ContT () IO ()
forward = do
  callCC $ \skip -> do
    skip ()
    lift $ putStrLn "This is not executed"
  lift $ putStrLn "Control flow continues here"

main :: IO ()
main = runContT forward return

Невозможно выполнить именно то, что вы хотите. Чтобы понять, почему, рассмотрите этот пример:

mdo
  l
  c <- lift getChar
  l <- if c == 'a' then setjmp else return (return ())
  lift $ putStrLn "end"

Что это должно сделать?


Вы также можете вернуться назад к пропущенному коду. Вам просто нужно передать продолжение коду, который вы пропустили. Используя ваш пример, goto L2: L1: some code; goto END; L2: goto L1; END: return может быть реализован как:

import Control.Monad.Cont

forward :: ContT () IO ()
forward = do
  callCC $ \end -> do
    l1 <- callCC $ \l2 -> do
      callCC $ \l1 -> l2 l1
      liftIO $ putStrLn "In L1"
      end ()
    liftIO $ putStrLn "In L2"
    l1 ()
  liftIO $ putStrLn "End"

main :: IO ()
main = runContT forward return

Здесь мы передаем продолжение части, которую мы пропустили (l1) обратно во внешний код, чтобы он мог туда прыгать.