[Python-ideas] Cofunctions - Back to Basics

Arnaud Delobelle arnodel at gmail.com
Thu Oct 27 13:45:30 CEST 2011


On 27 October 2011 03:09, Greg Ewing <greg.ewing at canterbury.ac.nz> wrote:
> Still feeling in a peppish mood after the last round of PEP 335,
> I decided to revisit my cofunctions proposal. Last time round, I
> allowed myself to be excessively swayed by popular opinion, and
> ended up with something that lacked elegance and failed to address
> the original issues.
>
> So, I've gone right back to my initial idea. The current version
> of the draft PEP can be found here:
>
> http://www.cosc.canterbury.ac.nz/greg.ewing/python/generators/cd_current/cofunction-pep-rev5.txt
>
> together with updated examples here:
>
> http://www.cosc.canterbury.ac.nz/greg.ewing/python/generators/cd_current/Examples/
>
> and prototype implementation available from:
>
> http://www.cosc.canterbury.ac.nz/greg.ewing/python/generators/cofunctions.html
>
> In a nutshell, the 'cocall' syntax is gone. Inside a cofunction, if
> an object being called implements __cocall__, then a cocall (implicit
> yield-from) is performed automatically, otherwise a normal call is
> made.

Hi,  I've taken the liberty to translate your examples using a small
utility that I wrote some time ago and modified to mimic your proposal
(imperfectly, of course, since this runs on unpatched python).  FWIW,
you can see and download the resulting files here (.html files for
viewing, .py files for downloading):

    http://www.marooned.org.uk/~arno/cofunctions/

These don't need ``yield from`` and will work both in Python 2 and 3.
The functionality is implemented with three functions defined in
cofunctions.py: cofunction, costart, coreturn.


The only changes that  made to your examples are the following:

1. Add the line ``from cofunctions import *`` at the start of the file

2. Change

    codef f(x, y): ...

to

    @cofunction
    def f(cocall, x, y): ...

3. Change 'cocalls' to cofunctions from within cofunctions as follows:

    f(x, y)

becomes:

    yield cocall(f, x, y)

4.  Change return statements within cofunctions to as follows:

    return 42

becomes

    coreturn(42)

5. The 'value' attribute of StopIteration exceptions raised by
returning cofunctions becomes a 'covalue' attribute


Note that #1, #2 and #4 a trivial changes.  To perform #3 is actually
simple because trying to call a cofunction raises and exception
pointing out the incorrect call, so running the examples show which
calls to change to cocalls.  I could have avoided #5 but somehow I
decided to stick with 'covalue' :)

Here is, for example, the 'parser.py' example translated:

---------------------------- parser.py --------------------------
from cofunctions import *

import re, sys
pat = re.compile(r"(\S+)|(<[^>]*>)")

text = "<foo> This is a <b> foo file </b> you know. </foo>"

def run():
  parser = costart(parse_items)
  next(parser)
  try:
    for m in pat.finditer(text):
      token = m.group(0)
      print("Feeding:", repr(token))
      parser.send(token)
    parser.send(None) # to signal EOF
  except StopIteration as e:
    tree = e.covalue
    print(tree)

@cofunction
def parse_elem(cocall, opening_tag):
  name = opening_tag[1:-1]
  closing_tag = "</%s>" % name
  items = yield cocall(parse_items, closing_tag)
  coreturn((name, items))

@cofunction
def parse_items(cocall, closing_tag = None):
  elems = []
  while 1:
    token = yield
    if not token:
      break # EOF
    if is_opening_tag(token):
      elems.append((yield cocall(parse_elem, token)))
    elif token == closing_tag:
      break
    else:
      elems.append(token)
  coreturn(elems)

def is_opening_tag(token):
  return token.startswith("<") and not token.startswith("</")

run()
--------------------------- /parser.py --------------------------

-- 
Arnaud



More information about the Python-ideas mailing list