A weird thing is that call-with-current-continuation doesn't yield back to something; it yields to the function given to call-with-current-continuation.
# yield jumps out of the 'receiver' function and never returns
print("This never prints")
x = callcc(receiver)
# f returns 2+1
# backtracks stores (continuation,[values]) pairs. Whenever a computation fails, we pull out another
# value from the list and feed it to the continuation. The idea is to perform a depth-first search.
backtracks = 
"""Aborts the current continuation and backtracks to another alternative stored in backtracks."""
if not backtracks: raise Exception('amb ran out of alternatives')
yield, alternatives = backtracks.pop()
alt = alternatives.pop()
if alternatives: # this continuation still has alternatives, so put them back on the list in case of a future 'fail'
"""The "ambivalent operator." The arguments to amb are alternatives, and the return value of amb
is an alternative which makes the future computation not fail."""
if not alternatives: fail()
if not b: fail()
"""Let's find a Pythagorean triple."""
x = amb(*range(1,100))
y = amb(*range(1,100))
z = amb(*range(1,100))
expect(x**2 + y**2 == z**2)
# We can print out all the Pythagorean triples.
trip = test_amb()
print("(x,y,z)=(%s,%s,%s)" % trip)
fail() # fail every time so it keeps backtracking, but the side effect of printing out remains
This page helped me quite a bit a few years ago
Multishot continuations are kind of like forks (but without the parallelism). You can save the program state and make copies of it so later on you can backtrack to the point of the fork if you want. With this you can do things like logic programming or define your own exception handling mechanism. However, this is hard to implement and is confusing of you mix with mutable state so very few languages have this feature...
BTW, one of the hard parts of understanding lisp continuations is that the api is "call with current continuation" (pass the continuation as a function arg) instead of "get current continuation" (which would return the continuation)
x <- foo
y <- bar (x+1)
bar x = Cont (\fr -> ...)
bar x fr = ...
fr are all continuations that come after us, reified as a function. We can use the type of bar
bar :: env -> (next-> result) -> result
Easiest way to get a result value is to calculate a next value from env. But we can do much more fun things as well, like implementing control jumps. Lets look at how we can implement this jumping:
jumpCont env _ignoredRestOfBlock = jumpTarget (...env)
callCC comnputeInnerBlock = \_ignoredEnv jumpTarget -> comnputeInnerBlock jumpCont
where jumpCont env _ignoredRestOfBlock = jumpTarget env
callCC $ \exitBlock -> do
x <- foo
when (x < 3) exitBlock
y <- bar (x+1) -- <- here until the rest of the callCC block would be _ignoredRestOfBlock
lift $ print (x*y)
... -- <- this is jumpTarget, the point callCC would jump to