[pypy-svn] r34348 - pypy/dist/pypy/doc
arigo at codespeak.net
arigo at codespeak.net
Tue Nov 7 20:44:26 CET 2006
Author: arigo
Date: Tue Nov 7 20:44:25 2006
New Revision: 34348
Modified:
pypy/dist/pypy/doc/_ref.txt
pypy/dist/pypy/doc/stackless.txt
Log:
Start talking about composability.
Modified: pypy/dist/pypy/doc/_ref.txt
==============================================================================
--- pypy/dist/pypy/doc/_ref.txt (original)
+++ pypy/dist/pypy/doc/_ref.txt Tue Nov 7 20:44:25 2006
@@ -36,6 +36,7 @@
.. _`module/_sre/`:
.. _`pypy/module/_sre`: ../../pypy/module/_sre
.. _`pypy/module/_sre/interp_sre.py`: ../../pypy/module/_sre/interp_sre.py
+.. _`pypy/module/_stackless/test/test_composable_coroutine.py`: ../../pypy/module/_stackless/test/test_composable_coroutine.py
.. _`pypy/module/readline`: ../../pypy/module/readline
.. _`module/recparser/`: ../../pypy/module/recparser
.. _`module/sys/`: ../../pypy/module/sys
Modified: pypy/dist/pypy/doc/stackless.txt
==============================================================================
--- pypy/dist/pypy/doc/stackless.txt (original)
+++ pypy/dist/pypy/doc/stackless.txt Tue Nov 7 20:44:25 2006
@@ -278,10 +278,141 @@
Composability
+++++++++++++
-XXX, without having to complicate each part of the
-application with explicit knowledge about the other part's usage of
-concurrency. Many traditional concurrency concepts cannot be composed
-in this way. XXX
+Although coroutines exist since a long time, they have not been
+generally integrated into mainstream languages, or only in limited form
+(like generators in Python and iterators C#). We can argue that a
+possible reason for that is that they do not scale well when a program's
+complexity increases: they look attractive in small examples, but the
+models that require explicit switching, by naming the target coroutine,
+do not compose naturally. This means that a program that uses
+coroutines for two unrelated purposes may run into conflits caused by
+unexpected interactions.
+
+To illustrate the problem, consider the following example (simplified
+code; see the full source in
+`pypy/module/_stackless/test/test_composable_coroutine.py`_). First, a
+simple usage of coroutine::
+
+ main_coro = coroutine.getcurrent() # the main (outer) coroutine
+ data = []
+
+ def data_producer():
+ for i in range(10):
+ # add some numbers to the list 'data' ...
+ data.append(i)
+ data.append(i * 5)
+ data.append(i * 25)
+ # and then switch back to main to continue processing
+ main_coro.switch()
+
+ producer_coro = coroutine()
+ producer_coro.bind(data_producer)
+
+ def grab_next_value():
+ if not data:
+ # put some more numbers in the 'data' list if needed
+ producer_coro.switch()
+ # then grab the next value from the list
+ return data.pop(0)
+
+Every call to grab_next_value() returns a single value, but if necessary
+it switches into the producer function (and back) to give it a chance to
+put some more numbers in it.
+
+Now consider a simple reimplementation of Python's generators in term of
+coroutines::
+
+ def generator(f):
+ """Wrap a function 'f' so that it behaves like a generator."""
+ def wrappedfunc(*args, **kwds):
+ g = generator_iterator()
+ g.bind(f, *args, **kwds)
+ return g
+ return wrappedfunc
+
+ class generator_iterator(coroutine):
+ def __iter__(self):
+ return self
+ def next(self):
+ self.caller = coroutine.getcurrent()
+ self.switch()
+ return self.answer
+
+ def Yield(value):
+ """Yield the value from the current generator."""
+ g = coroutine.getcurrent()
+ g.answer = value
+ g.caller.switch()
+
+ def squares(n):
+ """Demo generator, producing square numbers."""
+ for i in range(n):
+ Yield(i * i)
+ squares = generator(squares)
+
+ for x in squares(5):
+ print x # this prints 0, 1, 4, 9, 16
+
+Both these examples are attractively elegant. However, they cannot be
+composed. If we try to write the following generator::
+
+ def grab_values(n):
+ for i in range(n):
+ Yield(grab_next_value())
+ grab_values = generator(grab_values)
+
+then the program does not behave as expected. The reason is the
+following. The generator coroutine that executes ``grab_values()``
+calls ``grab_next_value()``, which may switch to the ``producer_coro``
+coroutine. This works so far, but the switching back from
+``data_producer()`` to ``main_coro`` lands in the wrong coroutine: it
+resumes execution in the main coroutine, which is not the one from which
+it comes. We expect ``data_producer()`` to switch back to the
+``grab_next_values()`` call, but the latter lives in the generator
+coroutine ``g`` created in ``wrappedfunc``, which is totally unknown to
+the ``data_producer()`` code. Instead, we really switch back to the
+main coroutine, which confuses the ``generator_iterator.next()`` method
+(it gets resumed, but not as a result of a call to ``Yield()``).
+
+As part of trying to combine multiple different paradigms into a single
+application-level module, we have built a way to solve this problem.
+The idea is to avoid the notion of a single, global "main" coroutine (or
+a single main greenlet, or a single main tasklet). Instead, each
+conceptually separated user of one of these concurrency interfaces can
+create its own "view" on what the main coroutine/greenlet/tasklet is,
+which other coroutine/greenlet/tasklets there are, and which of these is
+the currently running one. Each "view" is orthogonal to the others. In
+particular, each view has one (and exactly one) "current"
+coroutine/greenlet/tasklet at any point in time. When the user switches
+to a coroutine/greenlet/tasklet, it implicitly means that he wants to
+switch away from the current coroutine/greenlet/tasklet *that belongs to
+the same view as the target*.
+
+The precise application-level interface has not been fixed yet; so far,
+"views" in the above sense are objects of the type
+``stackless.usercostate``. The above two examples can be rewritten in
+the following way::
+
+ producer_view = stackless.usercostate() # a local view
+ main_coro = producer_view.getcurrent() # the main (outer) coroutine
+ ...
+ producer_coro = producer_view.newcoroutine()
+ ...
+
+and::
+
+ generators_view = stackless.usercostate()
+
+ def generator(f):
+ def wrappedfunc(*args, **kwds):
+ g = generators_view.newcoroutine(generator_iterator)
+ ...
+
+ ...generators_view.getcurrent()...
+
+Then the composition ``grab_values()`` works as expected.
+
+XXX a few words about how
.. _`Stackless Python`: http://www.stackless.com
More information about the Pypy-commit
mailing list