Hi,<br><br>Sorry I missed this one.  This is a very nice idea!  With respect to using decorators like this,<br>I like the &quot;you can&#39;t do that....oh, wait, yes you can&quot; feel of it.  Very creative.<br><br>I don&#39;t have time to read through the entire thread, but I did skim most of it and have some<br>
comments.<br><br>One of the big issues we have run into with the parallel computing stuff in IPython, <br>is that it is very tough to avoid typing code in strings.  Then you send the string to<br>a different execution context and it does exec in its namespace.  Here is an example:<br>
<br>mec.push(dict(a=10))<br>mec.execute(&#39;b = 2*a&#39;)<br><br>Fernando&#39;s original work on using &quot;with&quot; for stuff like this was to try to get something<br>that allowed you to just type your code:<br><br>a=10<br>
with remote_engine:<br>  b = 2*a<br><br>While this is much nicer, you do need some code to grab the value of a from the<br>top-level and push it over to the execution context.  I guess you could also<br>pass it to the context though like remote_engine(dict(a=10))<br>
<br>With Fernando&#39;s new idea, this example would read:<br><br>@remote(dict(a=10)<br>def foo():<br>    b = 2*a<br><br>While I wish we could get rid of the 2 line header (@remote...def foo), this is a pretty<br>nice way of abstracting this.  No code in strings, and a simple way of passing in variables.<br>
My only complaint is that is a bit unexpected that this actually declares and *calls* the function!<br>But renaming remote to call_remote or something would help.<br><br>I am going to start working on the parallel stuff in about 2 weeks, and I will revisit this then.  It shouldn&#39;t be <br>
too difficult to implement some nice things using this pattern.<br><br>Cheers,<br><br>Brian<br><br><br><br><br><br><div class="gmail_quote">On Fri, Sep 4, 2009 at 1:31 AM, Fernando Perez <span dir="ltr">&lt;<a href="http://fperez.net">fperez.net</a>@<a href="http://gmail.com">gmail.com</a>&gt;</span> wrote:<br>
<blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">Hi all,<br>
I know I should have been hard at work on continuing the branch review<br>
work I have in an open tab of Brian&#39;s push, but I couldn&#39;t resist.<br>
Please bear with me, this is  a bit technical but, I hope, very<br>
interesting in the long run for us...<br>
This part of Ars Technica&#39;s excellent review of Snow Leopard:<br>
<a href="http://arstechnica.com/apple/reviews/2009/08/mac-os-x-10-6.ars/13" target="_blank">http://arstechnica.com/apple/reviews/2009/08/mac-os-x-10-6.ars/13</a><br>
shows how Apple tackled the problem of providing civilized primitives<br>
to express parallellism in applications and a mechanism to make useful<br>
decisions based on this information.   The idea is to combine a<br>
kernel-level dispatcher (GCD), a beautiful extension to the C language<br>
(yes, they extended C!) in the form of anonymous code blocks, and an<br>
API to inform GCD of your parallelism breakup easily, so GCD can use<br>
your intentions at runtime efficiently.  It&#39;s really a remarkably<br>
simple, yet effective (IMHO) combination of tools to tackle a thorny<br>
In any case, what does all this have to do with us?  For a long time<br>
we&#39;ve wondered about how to provide the easiest, simplest APIs that<br>
appear natural to the user, that are easy to convert into serial<br>
execution mode trivially (with a simple global switch for debugging,<br>
NOT changing any actual code everywhere), and that can permit<br>
execution via ipython.  A while ago I hacked something via &#39;with&#39; and<br>
context  managers, that was so horrible and brittle (it involved stack<br>
manipulations, manual source introspection and exception injection)<br>
that I realized that could never really fly for production work.<br>
But this article on GCD got me trying my &#39;with&#39; approach again, and I<br>
realized that syntactically it felt quite nice, I could write python<br>
versions of the code examples in that review, yet the whole &#39;with&#39;<br>
mess killed it for me.  And then it hit me that decorators could be<br>
abused just a little bit to get the same job done [1]!  While this may<br>
be somewhat of an abuse, it does NOT involve source introspection or<br>
stack manipulations, so in principle it&#39;s 100% kosher, robust python.<br>
A little weird the first time you see it, but bear with me.<br>
The code below shows an implementation of a simple for  loop directly<br>
and via a decorator.  Both versions do the same thing, but the point<br>
is that by providing such decorators, we can *trivially* provide a<br>
GCD-style API for users to express their parallelism and have<br>
execution chunks handled by ipython remotely.<br>
It&#39;s obvious that such decorators can also be used to dispatch code to<br>
Cython, to a GPU,  to a CorePy-based optimizer, to a profiler, etc.  I<br>
think this could be a useful idea in more than one context, and it<br>
certainly feels to me like one of the missing API/usability pieces<br>
we&#39;ve struggled with for the ipython distributed machinery.<br>
[1] What clicked in my head was tying the &#39;with&#39; mess to how the Sage<br>
notebook uses the @interact decorator to immediately call the<br>
decorated function rather than decorating it and returning it.  This<br>
immediate-consumption (ab)use of a decorator is what I&#39;m using.<br>
### CODE example<br>
# Consider a simple pair of &#39;loop body&#39; and &#39;loop summary&#39; functions:<br>
def do_work(data, i):<br>
    return data[i]/2<br>
def summarize(results, count):<br>
    return sum(results[:count])<br>
# And some &#39;dataset&#39; (here just a list of 10 numbers<br>
count = 10<br>
data = [3.0*j for j in range(count) ]<br>
# That we&#39;ll process.  This is our processing loop, implemented as a regular<br>
# serial function that preallocates storage and then goes to work.<br>
def loop_serial():<br>
    results = [None]*count<br>
    for i in range(count):<br>
        results[i] = do_work(data, i)<br>
    return summarize(results, count)<br>
# The same thing can be done with a decorator:<br>
def for_each(iterable):<br>
    &quot;&quot;&quot;This decorator-based loop does a normal serial run.<br>
    But in principle it could be doing the dispatch remotely, or into a thread<br>
    pool, etc.<br>
    def call(func):<br>
        map(func, iterable)<br>
    return call<br>
# This is the actual code of the decorator-based loop:<br>
def loop_deco():<br>
    results = [None]*count<br>
    def loop(i):<br>
        results[i] = do_work(data, i)<br>
    return summarize(results, count)<br>
# Test<br>
assert loop_serial() == loop_deco()<br>
print &#39;OK&#39;<br>
IPython-dev mailing list<br>
<a href="mailto:IPython-dev@scipy.org">IPython-dev@scipy.org</a><br>
<a href="http://mail.scipy.org/mailman/listinfo/ipython-dev" target="_blank">http://mail.scipy.org/mailman/listinfo/ipython-dev</a><br>