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.
def f():
def receiver(yield):
yield(2)
# yield jumps out of the 'receiver' function and never returns
print("This never prints")
x = callcc(receiver)
return x+1
print(f())
# 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 = []
def fail():
"""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'
backtracks.push((yield, alternatives))
yield(alt)
def amb(*alternatives):
"""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()
def receiver(yield):
backtracks.append((yield, alternatives))
fail()
return callcc(receiver)
def expect(b):
if not b: fail()
def test_amb():
"""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)
return (x,y,z)
# 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 remainsThis page helped me quite a bit a few years ago http://blog.theincredibleholk.org/blog/2013/11/27/continuati...
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)
do
x <- foo
y <- bar (x+1)
baz (x*y)
let's look at bar: bar x = Cont (\fr -> ...)
Or without the type wrapper: bar x fr = ...
x represents the environment - all variables that are in scope and can be used to compute the next step. They represent everything that came before bar.fr are all continuations that come after us, reified as a function. We can use the type of bar
bar :: env -> (next-> result) -> result
to find ways to use continuations. Time for type tetris!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)
We ignore the function that represents the rest of our current block and use something else instead. Now we just have to figure out what 'something else' might be: callCC comnputeInnerBlock = \_ignoredEnv jumpTarget -> comnputeInnerBlock jumpCont
where jumpCont env _ignoredRestOfBlock = jumpTarget env
So we have two streams of control - the one callCC is part of and one nested within callCC. If we call jumpCont we ignore the rest of the nested block and continue with the callCC control stream, exiting the current block: 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
So if normal functions take the result of what came before as an argument and compute a new result, continuations take the result of what came before and a function that represents the rest of the computation and compute a new result. You can think of this function as created for you by some compiler magic.