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 yieldnext 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 values 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 cooperative
def 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!