Direct proxies update

Claus Reinke claus.reinke at
Tue Nov 29 08:33:17 PST 2011

> .. The initial intention (which is still strongly there but a
> bit changed) is that when you manipulate something in memory, you do
> something synchronous ("var a = f();"), but when doing an I/O, you do
> something asynchronous ("f(function(a){});"). This is a very elegant
> model, but what when the database is on memory?
> I think that we (node, Mirror API, a lot of people in the JavaScript
> community) are trying to achieve 2 goals that do not seem really going
> well together:
> 1) uniform API
> 2) support for both synchronous and asynchronous
> It seems to me that 2) imposes programming styles that impact APIs
> (returning a function or passing the result of a call as an argument of
> the given callback).
> I can't think of any language that is able to do both.

Just wanted to answer this comment: it is possible to smooth over
the differences between these two styles, by introducing generality
that is only needed in one of the two. Several languages support
syntax that makes the general case easier to write (links below),
and I think that ES should have such support, too.

Starting with the synchronous code pattern

    var a = f(); ..code..

assuming 'a' is for local use in '..code..' , this can be rewritten to

    ( function(a) { ..code.. } )( f() );

Now, to add a little flexibility, imagine a function 'then', to pass
'f's result to 'a' in the simplest case ("call f, _then_ pass its result
to a"), or to add additional steps if necessary (eg, error handling)

    then( function(a) { ..code.. } )( f() );

The unnamed function is now a callback parameter, and it is
customary to pass such as the last argument, so lets switch to

    then( f() )( function(a) { ..code.. } );

and if we want to cater for different kinds of 'then' (error
handling, asynch code, ..), we might want to turn 'then' into
a method of 'f's result

    f().then( function(a) { ..code.. } );

which is close to the second style you mentioned, but with an
important refinement: instead of every operation taking a
callback, every operation returns an object that conforms to
a simple interface (having a method 'then' which takes a
callback). Promises just happen to be 'then'-ables that work

So, if we had syntactic sugar for transforming, say,

    let a <- f(); ..code..    // (1)

into (modulo 'this'/'arguments', as usual)

    f().then( function(a) { ..code.. } );

Then we could write code (1) that looks similar (but not equal!)
to normal synchronous imperative code, no matter whether
the 'then' method in question directly passes the result to the
callback, or whether it registers the callback with a result that
happens to be a promise.

Having this kind of syntax sugar for working with 'then'-ables
is worth it (you'll also want to support the special case where
'f's result value is ignored) because the 'then'-able pattern works
for many more cases than just synch vs async, and because the
bulk of the work goes into the libraries (which are easier to
evolve and extend), not into the language (which just makes
the libraries possible/syntactically useable).

(for instance, since you have full access to the callback and the
intermediate results, you can build in error handling, or even
backtracking search/parsing, and many of the individual solutions
compose to form new 'then'-ables, eg. asynch+error handling).

Not surprisingly, none of this is new, so ES could borrow proven
ideas from other languages!-) Monad comprehensions and
do-notation in Haskell [1], computation expressions and asynch
workflows in F# [2], the proposed Erlando parse-transformers
for Erlang [3], ..


Don Syme's blog entry shows how the F# feature is remarkably
similar in style to quasi's, just over statement blocks
instead of strings (one has a bit of syntax and some code that
helps interpreting that syntax). An alternative view is as proxies
for blocks, rather than objects (one defines traps for each piece
of block syntax):


On the application to remote/local object: it is often considered
bad style to make remote work look like local work (completely
different considerations and characteristics apply regarding
latencies, communication and remote computation failures, etc.),
and we can consider it dangerous to make the complex case
(asynch/remote) look exactly like the simple case (synch/local).

We can, however, make the simple case look like the complex
case, eg, we can make local work look like remote work (local
just happens to be a fast and reliable remote worker that never
encounters many of the failure modes we need to handle in
general), or we can make synch code look like asynch code
(that just happens to be executed without delays).

We can then think about making the complex case easier to
write, so much so that using the complex code pattern for
simple use cases is no longer considered ridiculous - the
code for the complex case is almost as easy to write and
read as the code for the simple case.

As a welcome extra benefit, as the complex code becomes
easier to write, forgetting proper error handling becomes
less likely, and error handling patterns can be factored out
instead of being repeated and oversimplified everywhere.



More information about the es-discuss mailing list