Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Iterate, Generate, Cooperate

Sean Vieira
September 12, 2013

Iterate, Generate, Cooperate

Iterators, Generators, and Coroutines by example in Python and ECMAScript 6.

Given at a Lab49 DC Tech Breakfast.

Sean Vieira

September 12, 2013
Tweet

More Decks by Sean Vieira

Other Decks in Programming

Transcript

  1. In the beginning … There was jmp: loop: mov EAX,

    1 mov ECX, 10 add EAX, ECX cmp EAX, 40 jg continue jmp loop continue: ; And on we go!
  2. In the beginning … But that was error prone. So

    higher-level abstractions were created.
  3. Step #1: Abstract First, there were loops: for (int i

    = 0; i < some.length; i++) { } while (i-- > 0) { } do { } while (i-- > 0);
  4. Then someone said … # Copying and pasting bounds checking

    # all over the place doesn’t seem right. # Isn’t there an easier way? # What if we could get down to this? for variable in iterable: print(variable)
  5. Step #2: Abstract again iterator 1. An object that allows

    a programmer to traverse through all the elements of a collection, regardless of its specific implementation. http://en.wikipedia.org/wiki/Iterator
  6. Step #2: Abstract again class Iterable: def __init__(self): self._items =

    (1, 2, 3, 4, 5) self._cur = 0 def __iter__(self): return self def __next__(self): if self._cur > 4: raise StopIteration() current = self._items[self._cur] self._cur += 1 return current
  7. Step #3: Abstract again generator 1. A special routine that

    can be used to control the iteration behaviour of a loop. http://en.wikipedia.org/wiki/Generator
  8. Step #3: Abstract again def sequence(): """Let the interpreter handle

    state""" yield 1 yield 2 yield 3 yield 4 yield 5
  9. Positive benefits include def file_sequence(): """Letting the interpreter handle state"""

    with open("some-file") as fo: data = fo.read(1024) while data: yield data data = fo.read(1024) def infinite_sequence(): """Saving money on RAM""" i = 0 while True: yield i i += 1
  10. Positive benefits include def stateful_sequence(): """Being resumable""" with open("some-file") as

    fo: data = fo.read(1024) while data: yield data data = fo.read(1024)
  11. Well, that’s rather useful >>> whats_this = stateful_sequence() >>> next(whats_this)

    1 KB of text >>> print("Doing some other work") Doing some other work >>> next(whats_this) The next KB of text But how often do you need to do that … really?
  12. However ... What if you could send as well as

    receive? >>> whats_this = magic() >>> next(whats_this) 1 KB of text >>> print("Doing some other work") Doing some other work >>>whats_this.send("Some other data") The next KB of text and Some other data
  13. Introducing Coroutines coroutine 1. Coroutines are computer program components that

    generalize subroutines to allow multiple entry points for suspending and resuming execution at certain locations. http://en.wikipedia.org/wiki/Coroutine
  14. Introducing Coroutines coroutine 1. Coroutines are well-suited for implementing more

    familiar program components such as cooperative tasks, exceptions, event loop[s], iterators, infinite lists and pipes. http://en.wikipedia.org/wiki/Coroutine
  15. Easy state machines STARTED, IN_PROCESS, FINISHED = object(), object(), object()

    def StateMachine(): data = yield STARTED while True: should_terminate = process_data(data) if should_terminate: yield FINISHED break data = yield IN_PROCESS
  16. Easy state machines >>> state_machine = StateMachine() >>> state_machine.send(None) STARTED

    >>> state_machine.send(3) Processing 3 IN_PROGRESS >>> state_machine.send(22) Processing 22 IN_PROGRESS >>> state_machine.send(117) Processing 117 FINISHED
  17. Pipelines def producer(n): yield from range(n) @coroutine def filter(div, drain):

    while True: data = yield if not data % div: drain.send(data) @coroutine def transform(op, drain): while True: data = yield new_data = op(data) drain.send(new_data)
  18. Pipelines @coroutine def printer(): while True: data = yield print(data)

    drain = printer() pipeline = transform(lambda n: n * 2, drain) pipeline = filter(2, pipeline) # Alternately pipeline = filter(2, transform(lambda n: n * 2, printer())) data = producer(10) for val in data: pipeline.send(val)
  19. Broadcasting (Event Fan-out) @coroutine def EventNode(*listeners): while True: data =

    yield for listener in listeners: listener.send(data) drain = printer() double_and_print = transform(lambda n: n * 2, drain) filter_and_print = filter(2, drain) event_node = EventNode(double_and_print, filter_and_print) data = producer(10) for val in data: event_node.send(val)
  20. Process cooperation @coroutine def EventLoop(*actors): actors = list(actors) while True:

    actor = actors.pop(0) try: actor.send() actors.append(actor) catch GeneratorExit: continue
  21. Meanwhile, on the web function *EchoServer() { let response =

    yield null; while (true) { response = yield response; } } var echoServer = EchoServer(), server = loggingDecorator(echoServer); function *loggingDecorator(gen) { let processed = gen.next(null); while (!processed.done) { let unprocessed = yield processed.value; processed = gen.next(unprocessed); console.log("INFO:", processed); } } server.next("Hello"); server.next("World");
  22. Meanwhile, on the web // Via http://tobyho.com/2013/06/16/what-are-generators/ function run(genfun){ //

    instantiate the generator object var gen = genfun(); function next(err, answer){ var res; if (err){ // if err, throw it into the generator return gen.throw(err); }else{ // if good value, send it res = gen.next(answer); } if (!res.done){ // if we are not at the end we have an async request to fulfill // we do this by calling `value` as a function // and passing it a callback that accepts two positional arguments, `err` and `answer` // for which we'll just use `next()` res.value(next); } } // Kick off the async loop next(); }
  23. Meanwhile, on the web // Via http://tobyho.com/2013/06/16/what-are-generators/ function readFile(filepath){ return

    function(callback){ fs.readFile(filepath, callback); } } run(function*(){ try{ var tpContent = yield readFile('blog_post_template.html'); var mdContent = yield readFile('my_blog_post.md'); resp.end(template(tpContent, markdown(String(mdContent)))); }catch(e){ resp.end(e.message); } });
  24. Parting Thoughts (via David Beazley) “If you are going to

    use coroutines, it is critically important to not mix programming paradigms together There are three main uses of yield • Iteration (a producer of data) • Receiving messages (a consumer) • A trap (cooperative multitasking) Do NOT write generator functions that try to do more than one of these at once.”
  25. Suggested Further Reading • A Curious Course on Coroutines and

    Concurrency by David Beazley • Generators in V8 by Andy Wingo • Analysis of generators and other async patterns in node by Gorgi Kosev • Tortoises, Teleporting Turtles, and Iterators by Reginald Braithwaite • Programming in Lua Chapter 9 by Roberto Ierusalimschy • Wu.js by Nick Fitzgerald • Suspend.js by Jeremy Martin • A Closer Look at Generators without Promises by James Long