6.S050
Created: 2023-05-11 Thu 11:06
def powers_of_2(): x = 1 while True: yield x x *= 2 for (i, n) in zip(range(5), powers_of_2()): print(f'2 ** {i} == {n}')
Removing some syntactic sugar…
def powers_of_2(): x = 1 while True: yield x x *= 2 gen = powers_of_2() for i in range(5): n = next(gen) print(f'2 ** {i} == {n}')
yield
"returns" a value to the callernext()
is called, execution resumes after yield
next
is called)Resume continuation:
while True:
□
x *= 2
Return continuation:
for i in range(5):
n = □
print(f'2 ** {i} == {n}')
def gen():
yield 4
yield 2
i = gen()
print([next(i), next(i)])
shift
and reset
)yield
, like generatorscreate
: create a coroutine and return a handle
resume coroutine value
: resume coroutine, with value
returned at yield point
coroutine.send(value)
yield value
: transfer control to the resume point of current coroutine, returning value
s
matches pattern ("abc"|"de")."x"
Source: "Revisiting Coroutines", De Moura & Ierusalimschy 2009
Proposal: Represent goals as coroutines that yield when they succeed.
def prim(lit):
def match(s, pos):
if s[pos:].startswith(lit):
yield (pos + len(lit))
return match
def alt(pat1, pat2):
def match(s, pos):
yield from pat1(s, pos)
yield from pat2(s, pos)
return match
What's up with yield from
?
def seq(pat1, pat2):
def match(s, pos):
for mid_pos in pat1(s, pos):
yield from pat2(s, mid_pos)
return match
async=/=await
in JavaScript/Python are cooperativedef do_stuff(): while True: result = wait_for_results(4) print(f'Did stuff: {result}') def do_more_stuff(): while True: result = wait_for_results(1) print(f'Did more stuff: {result}') run([do_stuff, do_more_stuff])
Can we make progress on do_more_stuff
while we wait in do_stuff
?
Coroutines give us exactly what we need:
def do_stuff(): while True: result = yield from wait_for_results(4) print(f'Did stuff: {result}')
def run(tasks): running = ... # running tasks waiting = ... # waiting tasks while True: for task in running: # run task to next yield for task in waiting: # if task is done waiting, put back on running queue
def run(tasks): # keep track of what to return to task running = [(t, None) for t in tasks] waiting = ... # waiting tasks while True: for task in running: # run task to next yield for task in waiting: # if task is done waiting, put back on running queue
def run(tasks): running = [(t, None) for t in tasks] waiting = [] while True: for task in running: # run task to next yield for (t, v) in running: match t.send(v): # task yielded a "request" to call wait case ("wait", n): waiting.append((t, n)) case _: pass running = [] for task in waiting: # if task is done waiting, put back on running queue
def run(tasks): running = [(t, None) for t in tasks] waiting = [] while True: for task in running: # run task to next yield, add to wait queue if needed # decrement waiting time waiting = [(t, n - 1) for (t, n) in waiting] for task in waiting: # if task is done waiting, put back on running queue
def run(tasks): running = [(t, None) for t in tasks] waiting = [] while True: for task in running: # run task to next yield, add to wait queue if needed # decrement waiting time waiting = [(t, n - 1) for (t, n) in waiting] # if task is done waiting, put back on running queue for (t, n) in waiting: if n == 0: running.append((t, 'done waiting!')) # remove tasks from waiting queue if done waiting = [(t, n) for (t, n) in waiting if n > 0]
def wait_for_results(n):
x = yield ("wait", n)
return x
def task(n):
while True:
x = yield from wait_for_results(n)
print(f"Task {n}: {x}")
run([task(1), task(10), task(3)])
Task 1: done waiting! Task 1: done waiting! Task 3: done waiting! Task 1: done waiting! Task 1: done waiting! Task 1: done waiting! Task 3: done waiting! Task 1: done waiting! Task 1: done waiting! Task 1: done waiting! Task 3: done waiting! Task 1: done waiting!