I'm really really struggling with understanding callCC. I get the power of Continuations and I've been using the concept in some of my projects to create cool concepts. But never have I needed to use something with greater capabilities than cont :: ((a->r)->r)-> Cont r a
.
After using it, it makes a lot of sense why they call the Cont Monad the mother of all monads, YET, I don't get when would I need to use callCC
, and that's exactly my question.
callCC
gives you "early return" semantics, but in a monadic context.
Say you wanted to doOne
, and if that returns True
, you immediately stop, otherwise you go on to doTwo
and doThree
:
doOne :: Cont r Bool
doTwo :: Cont r ()
doThree :: Cont r ()
doThings :: Cont r ()
doThings = do
one <- doOne
if one
then pure ()
else do
doTwo
doThree
See that if
branching there? One branch is not that bad, could be dealt with, but imagine there are multiple such points where you just want to bail? This gets very ugly very quickly.
With callCC
you can have "early return": you bail at the branching point and don't have to nest the rest of the computation:
doThings = callCC \ret -> do
one <- doOne
when one $ ret ()
doTwo
doThree
Much more pleasant to read!
More importantly, since ret
here is not a special syntax (like return
in C-like languages), but just a value like any other, you can pass it to other functions too! And those functions can then perform what's called "non-local return" - i.e. they can "stop" the doThings
computation, even from multiple nested calls deep. For example, I could factor out checking of the doOne
's result into a separate function checkOne
like this:
checkOne ret = do
one <- doOne
when one $ ret ()
doThings = callCC \ret -> do
checkOne ret
doTwo
doThree
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With