I am having trouble understanding the answers to a previous question. I'm hoping that an explanation of the following will clarify things. The following example comes from fpcomplete
import Control.Monad.Trans.Class
import Control.Monad.Trans.Cont
main = flip runContT return $ do
lift $ putStrLn "alpha"
(k, num) <- callCC $ \k -> let f x = k (f, x)
in return (f, 0)
lift $ putStrLn "beta"
lift $ putStrLn "gamma"
if num < 5
then k (num + 1) >> return ()
else lift $ print num
The output is
alpha
beta
gamma
beta
gamma
beta
gamma
beta
gamma
beta
gamma
beta
gamma
5
I think I understand how this example works, but why is it necessary to have a let
expression in the callCC
to "return" the continuation so that it can be used later on. So I tried to directly return the continuation by taking the following simpler example and modifying it.
import Control.Monad.Trans.Class
import Control.Monad.Trans.Cont
main = flip runContT return $ do
lift $ putStrLn "alpha"
callCC $ \k -> do
k ()
lift $ putStrLn "uh oh..."
lift $ putStrLn "beta"
lift $ putStrLn "gamma"
This prints
alpha
beta
gamma
And I modified it to the following
import Control.Monad.Trans.Class
import Control.Monad.Trans.Cont
main = flip runContT return $ do
lift $ putStrLn "alpha"
f <- callCC $ \k -> do
lift $ putStrLn "uh oh..."
return k
lift $ putStrLn "beta"
lift $ putStrLn "gamma"
The idea being that the continuation would get returned as f
and be unused in this test example which I would expect to print
uh oh...
beta
gamma
But this example doesn't compile, why can't this be done?
Edit: Consider the analgous example in Scheme. As far as I know Scheme wouldn't have a problem, is that correct?, but why?.
Looking at your examples in reverse order.
The last example does not typecheck due to an infinite type. Looking at the type of
callCC
, it is((a -> ContT r m b) -> ContT r m a) -> ContT r m a
. If we try to return the continuation we return something of typeContT r m (a -> ContT r m b)
. This means we get the type equality constrainta ~ (a -> ContT r m b)
, which meansa
has to be an infinite type. Haskell does not allow these (in general, for good reason - as far as I can tell the infinite type here would be something along the lines of, supply it an infinite order function as an argument).You don't mention whether there's anything you're confused about in the second example, but. The reason that it does not print "uh oh..." is because the
ContT
action produced byk ()
, unlike manyContT
actions does not use the following computation. This is the difference between the continuations and just normal functions which returnContT
actions (disclaimer, any function could return aContT
action like this, but in general). So, when you follow thek ()
up with a print, or anything else, it is irrelevant, because thek ()
just discards the following actions.So, the first example. The let binding here is actually only used to mess around with the parameters to
k
. But by doing so we avoid an infinite type. Effectively, we do some recursion in the let binding which is related to the infinite type we got before.f
is a little bit like a version of the continuation with the recursion already done.The type of this lambda we pass to
callCC
isNum n => ((n -> ContT r m b, n) -> ContT r m b) -> ContT r m (n -> ContT r m b, n)
. This does not have the same infinite type problem that your last example has, because we messed around with the parameters. You can perform a similar trick without adding the extra parameter by using let bindings in other ways. For example:This probably wasn't a terribly well explained answer, but the basic idea is that returning the continuation directly will create an infinite type problem. By using a let binding to create some recursion inside the lambda you pass to
callCC
, you can avoid this.