<div>Hi Terry,</div><div><br></div>This is a really nice approach. Thanks for sharing! Are there any downsides or functionality that can&#39;t be accomplished using this approach? This combined with the generator approach to deferreds will make it easier to reason about the code flow.<div>

<div><br></div><div>Naveen<br><br><div class="gmail_quote">On Sun, Oct 14, 2012 at 4:40 PM, Terry Jones <span dir="ltr">&lt;<a href="mailto:terry@jon.es" target="_blank">terry@jon.es</a>&gt;</span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">

This morning I was thinking about deferreds and how people find them<br>
difficult to grasp, but how they&#39;re conceptually simple once you get it.  I<br>
guess most of us tell people a deferred is something to hold a result that<br>
hasn&#39;t arrived yet. Sometimes, though, deferreds do have a result in them<br>
immediately (e.g., using succeed or fail to get an already-fired deferred).<br>
<br>
I wondered if it might work to tell people to think of a deferred as really<br>
being the result. If that were literally true, instead of writing:<br>
<br>
    d = getPage(...)<br>
    d.addErrback(errcheck, args)<br>
    d.addCallback(cleanup, args)<br>
    d.addCallback(reformat, args)<br>
    return d<br>
<br>
We might write something like:<br>
<br>
    result1 = getPage(...)<br>
    result2 = errcheck(result1, args)<br>
    result3 = cleanup(result2, args)<br>
    return reformat(result3, args)<br>
<br>
And if you could write that, you could obviously instead write:<br>
<br>
    return reformat(cleanup(errcheck(getPage(...), args), args), args)<br>
<br>
If we could write Twisted code that way, I think using deferreds would be<br>
simpler for people unfamiliar with them.<br>
<br>
In the style we&#39;re all used to, the programmer manually adds callbacks and<br>
errbacks.  That&#39;s basically boilerplate. It gets worse when you then need<br>
to also use DeferredList, etc. It&#39;s a little confusing to read deferred<br>
code at first, because you need to know that the deferred result/failure is<br>
automatically passed as the first arg to callbacks/errbacks.  It seems to<br>
take a year or more for people to finally realize how the callback &amp;<br>
errback chains actually interact :-) Also, I wonder how comfortable<br>
programmers are with code ordered innermost function first, as in the<br>
normal d.addCallback(inner).addCallback(outer) Twisted style, versus<br>
outer(inner()), as in the line above.<br>
<br>
Anyway... I realized we CAN let people use the succinct style above, by<br>
putting boilerplate into decorators.  I wrote two decorators, called<br>
(surprise!) callback and errback.  You can do this:<br>
<br>
    @errback<br>
    def errcheck(failure, arg):<br>
        ...<br>
<br>
    @callback<br>
    def cleanup(page, arg):<br>
        ...<br>
<br>
    @callback<br>
    def reformat(page, arg):<br>
        ...<br>
<br>
    reformat(cleanup(errcheck(getPage(...), arg1), arg2), arg3)<br>
<br>
The deferred callback and errback chains are hooked up automatically. You<br>
still get a regular deferred back as the return value.<br>
<br>
And... the &quot;deferred&quot; aspect of the code (or at least the need to talk<br>
about or explain it) has conveniently vanished.<br>
<br>
You can also do things like<br>
<br>
    func1(getDeferred1(), errcheck(func2(getDeferred2(), getDeferred3())))<br>
<br>
This gets the result of deferreds 2 &amp; 3 and (if neither fails) passes the<br>
result of calling func2 on both results through to func1, which is also<br>
called with the result of deferred 1. You don&#39;t need to use DeferredLists,<br>
as the decorator makes them for you. The errcheck function wont be called<br>
at all unless there&#39;s an error.<br>
<br>
That&#39;s nice compared to the verbose equivalent:<br>
<br>
    d1 = DeferredList([getDeferred2(), getDeferred3()])<br>
    d1.addCallback(func2)<br>
    d1.addErrback(errcheck)<br>
    d2 = DeferredList([getDeferred1(), d1])<br>
    d2.addCallback(func1)<br>
<br>
Or the more compact but awkward:<br>
<br>
    DeferredList([getDeferred(), DeferredList([getDeferred(), getDeferred()]).addCallback(func2).addErrback(errcheck)]).addCallback(func1)<br>
<br>
There&#39;s lots more that could be said about this, but that&#39;s enough for now.<br>
The code (surely not bulletproof) and some tests are at<br>
<a href="https://github.com/terrycojones/twisted-callback-decorators" target="_blank">https://github.com/terrycojones/twisted-callback-decorators</a><br>
<br>
I&#39;ll add a README sometime soon.  This is still pretty much proof of<br>
concept, and some it could be done slightly differently. I&#39;m happy to<br>
discuss in more detail if people are interested.<br>
<br>
Terry<br>
<br>
_______________________________________________<br>
Twisted-Python mailing list<br>
<a href="mailto:Twisted-Python@twistedmatrix.com">Twisted-Python@twistedmatrix.com</a><br>
<a href="http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python" target="_blank">http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python</a><br>
</blockquote></div><br></div></div>