- From: Tab Atkins Jr. <jackalmage@gmail.com>
- Date: Sat, 27 Apr 2013 09:47:16 -0700
- To: Ron Buckton <rbuckton@chronicles.org>
- Cc: David Bruant <bruant.d@gmail.com>, "Mark S. Miller" <erights@google.com>, "public-script-coord@w3.org" <public-script-coord@w3.org>, Mark Miller <erights@gmail.com>, Dean Tribble <tribble@e-dean.com>, es-discuss <es-discuss@mozilla.org>
On Sat, Apr 27, 2013 at 9:21 AM, Ron Buckton <rbuckton@chronicles.org> wrote: > Here is a case where flattening helps: [snip example code] > In this example, the server will receive a command from the client and will > process it asynchronously. The client then needs to poll an endpoint to > check for completion of the command. Just using Futures, flattening is > helpful here. Without flattening, executeAndWaitForComplete would could > return either a Future<object> OR return a Future<Future<object>>. Nope, this is the same error that David Bruant was making in his arguments. In the "monadic promise" proposal (as opposed to the "recursive unwrapping" proposal), a .then() callback can return a plain value *or a promise for a plain value*, and the output promise returned by .then() will be *the exact same*. In other words, in your example code, executeAndWaitForComplete() gets a promise from getJSON(), then calls .then() to chain some code after it and returns the resulting promise. The .then() callback either returns commandResult.model (a plain value), or returns a promise generated from pollForCommandComplete() (which eventually completes with pollResult.model). The only difference between the two code branches in .then() is that the first one (where it returns the model) accepts immediately with the model value, while the second one (where it returns a promise for the model) stays pending for a little while long, and then eventually accepts with the model value. To anything calling executeAndWaitForComplete(), the return result is *always* Promise<model>. It is *never* Promise<Promise<model>>. That's not something that can happen, because promises obey the monad laws, and that's how monads work. (A quick primer - anything is a "monad" if it obeys some really simple laws. It has to be some wrapper class around a value (it's more abstract than that, actually, but talking about wrappers is easy), which exposes some function that takes a callback, typically called "bind" or "flatMap". Calling .bind() is very similar to calling .map() on an array - .map() takes a function, and calls it for every element in the array, making a new array with the return results. The only difference is that the callback to .bind() is expected to return a value in the same monad, so you get double-wrapping. For example, if you called .map() and only returned arrays, you'd get an array of arrays as the result. The magic here that makes it a monad is that the class knows how to "flatten" itself one level, so it can take the return result of .bind(), which has a double-wrapped value, and turn it into a single-wrapped value. This is how .then() works on Promises - if you return a Promise<b> from the callback, you'll get a Promise<Promise<x>>, but the Promise class knows how to flatten itself by one level, so it does so and hands you back a simple Promise<x>. To help drive the concept home, say we did "[1, 2, 3].map(x=>[x, 10*x])". The return result would be "[[1,10],[2,20],[3,30]]". On the other hand, if we defined "bind" (/flatMap/chain/then) on Array, we could do the exact same thing "[1, 2, 3].bind(x=>[x, 10*x])", but the return result would be "[1, 10, 2, 20, 3, 30]" - it automatically flattens itself one level. (It's not obvious in this example, but it really does only do one level - if I'd returned [x, [10*x]] from the callback, the return from .bind() would be "[1, [10], 2, [20], 3, [30]]".)) ~TJ
Received on Saturday, 27 April 2013 16:48:03 UTC