-
Notifications
You must be signed in to change notification settings - Fork 165
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Only un-wrap one layer in [[Resolve]]
#101
Comments
First, we need to be much more precise with our usage of "promise" versus "thenable." I think it's important to note that the spec does not prohibit promises for thenables. An implementation is allowed to create such things, and operate with them, and even hand them out to others. However, the spec does specify that if a non-promise thenable is assimilated, i.e. an unrecognized thenable from another implementation, it must be flattened. This behavior is meant to preserve boundaries between implementations. That's all! Thus, allowing promises-for-thenables is an implementation choice left up to individual libraries. However, when libraries interact, such objects must not be forced upon an implementation that does support or allow such objects, i.e. that latter implementation must be protected from the design choices of a thenable-for-thenable producer. (I had another paragraph here, but am moving it over to the gist in an attempt to respect the rules, since it ended up sounding more like advocacy than clarification.) |
So the "argument collection" period seems to be over, and as I have said earlier (which I probably shouldn't have b/c of neutrality - for which I apologize) I feel for both standpoints. That's why I think it may be be counterproductive to turn this into a shoot-out of "are we going to have promises for promises - or not?". I think we would do best to first establish that we can't have both, or if we can't have both requirements in the way that that they are stated now, if we can't have the benefits of both with some creative solution. I already suggested for the "promises for promises" proponents to simply "cede ground" (or stop attempting to take the current castle, depending on how you view the current state of the spec), and instead attempt something that leaves the "no promises for promises" proponents satisfied. Most notably for me is that the people against "promises for promises" want something simple, while the proponents want something suitable for advanced use cases. Plus all or most have a FP background which I think could mean that calling the As illustration, I want to present this piece of evidence: function liftA2(pa, pb, f) {
return ap(pb, map(pa, function(a) {
return function(b) {
return f(a, b);
};
}));
}
The point is that @pufuwozu and others might see ways (beyond my imagination) to make great use of promises/nomads without ever having to see a single These are some ideas. 1 and 2 I mentioned earlier.
What do you think? |
My lack of experience with Promise implemantions may become awfully clear... |
Right, I now want to open this up to the floor for comments. Please ensure that you re-read each document. Many of them have changed significantly. You don't need to read the comments unless you want additional clarification. The comments in FOR discuss what it means for a promise to be lazy. The comments in AGAINST are largely about how you can recognize a foreign promise. Neither discussion relates directly to the issue at hand. I would now like to propose that all discussion take place in this issue. |
I want to begin with the clarification that the current spec does not prohibit promises for promises. It also doesn't prohibit the The advantage of this is that such a specification could be developed independently of any existing specifications and could then prove its usefulness through use in real applications. |
It would also mean that a library that didn't want to allow promises for promises could still recursively flatten such thenables/promises at the boundary. |
@ForbesLindesay Regardless of the spec, doesn't it also matter what the de-facto behavior is today? I mean, if the most prominent Promise libraries now all voluntarily choose to unwrap all layers, then that will be the expected behavior of consumers of said libraries. I.e. a consumer would expect that if it call "then" on some promise, the callback it supplies will get called with a non-promise value in return. Leaving this part of the spec open could be harmful then. Note, I'm not sure about the harmfulness. I've yet to gain experience with doing such advanced work on promises. Right now the only promises I've worked with were the promises I've created myself (by means of RSVP, that is). |
Perhaps it calls for two excplicit "variants", "additions" to the spec, because both of these can be regarded as something positive, but mutually exclude each other:
I think it would be a good thing if a programmer could see up front what kind of library he's getting: Variant 1 or variant 2. Then both could benefit from an explicit mark. Solely developing a distinct "mark" for (2) leaves ambiguity in the behavior for the other libraries. I.e. do they provide any guarantees on unwrapping all promises and thenables? Put differently, I think RSVP currently does more than it is asked for and - based on Domenic comments - this "more" is seen as a good thing. Better be explicit about it then. Simplicity is a selling point. (I'd personally opt for a library which respects the |
Actually the library I'd choose would depend on what I'd be using it for. If I'd be using the library for "scripting", I'd choose a library that guarantees unwrapping, so that if I call "then" I get a value that I can use straight away. If I'd be developing a library based on promises on my own (say promised-builtins) then I'd choose for the non-unwrapping variant, so I can make use of laziness inside. Actually, I think when coded right, a library of variety 2 could be easily made to adapt behavior of variant 1. All it needs is the |
@meryn please don't. This is a JavaScript specification, let's keep the discussion in the same language. @ForbesLindesay said:
This may seem that way, but in practice the story is very different. The lack of branding (which we discussed a while ago) left libraries with three options:
(3) is simply impractical and many times impossible. Q does (2). YUI, when and most notably DOMFuture do (1). In fact, take a look at the text in DOMFuture:
Emphasis mine. If I understood correctly, this effectively means that whatever thenable or promise will be flattened. So we have two options going forward:
|
@meryn I'm sorry, but you've thrown such amazingly large walls of text up that I can't really keep up. If you want to summarize your points to like 200 words maybe we could all comprehend them better. @juandopazo DOMFuture will almost certainly not change, and if But in practice vs. in theory doesn't really matter. The point is, many libraries in practice don't want to allow promises-for-promises. That includes, apparently, YUI, DOMFuture, WinJS, and when. (Q will join them soon, most likely, and DOMFuture has an inconsistency that it allows creation of promises-for-promises via Maybe let's approach this the other way: what possible benefit would you derive from a JQPFAQPFAWJPFADFFAN? If each of those implementations recognized the others as conformant, then you could create such an entity. Why is that a good thing? What does it help you with? |
@juandopazo I replaced it with original code from @pufuwozu . Works too. :) |
@domenic writes
I am very distressed to hear this. Likely it will mean that we will have to fork Q, as I don't see how my security goals can be met within (1). If this is not the right forum for this issue, please post a link to the right one. |
@erights perhaps I misspoke; I meant only from the perspective of the |
@domenic Read the first sentence of my bullet points. That will do as a summary I think. The summary of the summary is that I want to look for options to keep Another option (5), I just came up with: By default, let In general, I feel that the non-simple variant (the unwrapping behavior for then is certainly simple.. or should I say easy?) may be a little ugly, because it will often be hidden away behind abstractions (why I mentioned the |
@meryn what value do you derive from a JQPFAQPFAWJPFADFFAN? What problems does it help you solve? |
@domenic Ok, that wouldn't break security as far as I can see. But it would still break both promise pipelining (section 8 of http://www.erights.org/talks/promises/paper/tgc05.pdf . Nice summary at http://en.wikipedia.org/wiki/Futures_and_promises#Promise_pipelining ) and laziness. Please look again at the logic of Q.makeRemote in makeQ.js. |
@domenic I'm not arguing for a JQPFAQPFAWJPFADFFAN :) I'm arguing for support for both recursive and non-recursive unwrapping. The possibility of getting a JQPFAQPFAWJPFADFFAN may be an accidental consequence. This is something the people using the non-recursive unwrapping "mode" must deal with. It shouldn't be your worry. :) I personally will make sure I'll keep sanity in any "layered" promises. But "lazy promises" sure would be nice! |
@meryn writes
No conflict here, as demonstrated by the existence proof of Q.makeRemote in makeQ.js |
@meryn No conflict that is, as long as a promise library does not unwrap its own promises, which is the issue just raised about Q. |
@erights I need to study Q.makeRemote . My reference point is an idea of mine for making "Promised Builtins". Basically proxies for builtins that behave just like their real counterparts. I think it would be great if I could pass them along without wondering if some are lazy, and some are really already in the process of being resolved (i.e. the async calls have been fired off). And if make a call to I mentioned requiring implemenators not to unwrap their own promises in (2) of my earlier post. However, I do think that would result in a more complex API from the consumer point of view. The outward simplicity is the thing that @domenic likes a a lot, and I do too. |
@erights where do I find "makeQ.js"? |
Also, re JQPFAQPFAWJPFADFFAN: I think that with the proposed rule to only unwrap one layer, compliant libraries can never be the cause of this. Just replacing one foreign "promise layer" with a native promise-layer means the number of layers will stay equal. It won't decrease though. Any existing layering will remain. But this happens to protect the poor little lazy promise who doesn't want its I think the most typical case will be that most of the time, most promises will be single layered (i.e. they would directly resolve to a value) and some will be double-layered promises of promises. The outward layer would effectively protect the inner layer from being touched. I don't see more than two layers happening in practice. At least, it's beyond my imagination of usefulness. |
(facepalm) Sorry, I meant
You'd have to navigate the 6000 comments of the last few days to answer that. If I can put it in one sentence it would be: "doesn't break the monad laws and helps by introspection to discover opportunities for performance improvements". |
Why not the other way round? I'd be fine with that. I mean, of the many people who have argued against promises for promises and like the synchronous-code metapher, who of you has ever encountered a promise/thenable for a thenable? I'd assume they just don't exist in "your world", so you don't have to worry about obtaining one. And should you really (have to) work with a lib that (maybe) uses/supports this crazy feature ("has that bug"), then just set a "and-please-flatten-everything-for-me" flag when assimilating one of these promises. Those of us who are (planning to) use promises for promises in everyday code certainly don't want to have to care about always filling out that third parameter. |
They don't exist in our world because we flatten them. If we didn't, they'd be everywhere, as anyone who's tried to use jQuery and Q or when and Mongoose or WinJS and RSVP together can attest! |
@domenic Could you explain the typical process of how these layered promises come into existence? Would they still come into existence if every time a foreign promise was encountered, only the (foreign) outer layer gets replaced by a native layer? Because that's what up for discussion I think. (the outer layer could very well be the only layer, but that's beside the point). |
@meryn They wouldn't come into existence if all promise libraries worked like this and supported unwrapping one layer with each call to then. The reality is that many implementations don't unwrap at all. A chain of two sequential http requests could easily cause this problem if made using one of the non-conformant libraries. The current system protects you from the non-conformant libraries. |
I think maybe I wasn't clear here. I wasn't suggesting we use branding to say "I'm a promise so you should unwrap me" since there are loads of promise objects which lack such branding. I was suggesting we brand this new variety of promises that supports/requires nesting. This seems fine because there aren't many such libraries at the moment so it's not too late for them to all agree on something. It should also be easier because (in order for it to be useful) it seems to be necessary for them to support other operations such as |
If other paradigms aside from promises, such as those using |
@ForbesLindesay no, sorry. You were clear. I was just trying to make a point. |
@domenic that seems like a sensible idea to me. For anyone who's interested, a better way of building the Request API might be to have a separate |
@ForbesLindesay
While it of course doesn't matter for an implementation what exact "string" is used to set the property, I don't think I don't say my variant is right. Maybe coupling to the Promise/A+ "brand" is undesirable. |
@meryn I think you're still missing the point. We want to allow implementations to not do promises-for-promises. We can't specify some kind of magic brand which then allows those protections to be bypassed. In your phrasing, "I have not made the world any worse" is not something all libraries agree on. In my view, any library that produces promises for promises has made the world worse, and I need to put a stop to that craziness before it crosses into my own library. |
Again, I'm a bit surprised this is so hard. As I emphasized originally, you can do promises for promises as much as you want. You just need to stay within your own library. Things get normalized when crossing library boundaries, not before. |
Is this established? Is there a "we" who wants to allow implementations NOT to do promises-for-promises? You're entitled to your opinion of course, as are many others who might agree with you. |
@meryn You're still missing the point as @domenic says. We aren't going to punch a magic hole through the existing implementations. I think what we can go for is go for a solution where all the libraries that support promises for promises can work together, so the idea can be experimented with properly. |
Those essages crossed in the post slightly. We are discussing that option, but I think it's highly unlikely to succeed in that form. A great many libraries explicitly don't want to support promises for promises. |
@ForbesLindesay Thanks for pointing that out to me. I think I'll indeed keep quiet from now on. I hope that I've at least have added some value to the discussion. Otherwise, sorry for the noise. Let me know if there's a new spec up for debate. :) |
I would not like Promise libraries to provide an |
Just to clarify the consensus part, at least I am not sure if flattening is the best approach. I haven't explored the problem space enough. While on one hand I appreciate the arguments behind flattening, I can't shake the feeling that dismissing 30 years of research in monads is a pity. |
Flattening the one layer should prevent that, shouldn't it? Can you make a (code) example of how using these libs together results in an accidentally nested promise (if they didn't do recursion)?
That's what I was suspecting, and what I think we should specify if it's gonna work. And providing an alternative way for assimilating non-conformant thenables, instead of needing to assume that every thenable is non-conformant.
You mean they implement |
@pufuwozu wrote:
Hey, that could be a clever idea to detect conforming promises/thenables! I'd think of a [[resolve]] procedure such as
|
var nestedPromise = $.when(Q(5));
var anotherOne = $.when(5).then(function () { return Q(10); });
var realisticExample = $('.foo').animate('opacity', 0).promise().then(function () {
return getDataFromServerUsingQ();
}); Three levels: var _Deferred = require('underscore.deferred');
var threeLevels = _.when($.when(Q(5))); Etc. as I pile on more and more libraries. |
OK, but nesting constructors is something you wouldn't do, would you? Actually I'd say something like that (maybe with Your
or
as just going on with jQuery's |
@bergus yes, you would need to do a transformation into a realistic example for the three-level case; I left that as an exercise to the reader. Let's try again. This will also address your other question. var takeThree = getDataFromServerUsingQ().then(function (data) {
return $('.foo').animate('opacity', data.opacityLevel).promise().then(function () {
return updateBackboneModelViaSomeThirdPartyLibraryUsingUnderscoreDeferred().then(function () {
return tellServerThatTheUpdateSucceededUsingQ();
});
});
}); If Q, as a proper Promises/A+ library, does recursive |
I use promises a lot, worked out a lot of complex async flows with them, and I understand this discussion as: It's a dispute over a fact whether a promise is a real value (type that we treat same on same level as we treat an array, string, number etc.) or not (as it's treated now by flattening it to resolved value whenever possible). I take that if we treat it on pair with other types, then we're actually no longer talking about promises but about some other custom type, that serves uncertain purpose. It will no longer be a promise as it will no longer be aid for asynchronicity (as it is now with currently accepted characteristics). I'll add to @domenic example, copy of my example from Promise for Promises gist, of how with current common usage, lazy promises (not flattened) will behave: var lintDirectory = function (path) {
return readdir(path).map(function (fileName) {
return readFile(fileName).then(lint);
});
};
// No lazy promises (as current implementations do)
lintDirectory(path).then(function (reports) {
console.log(reports); // [...] array of lint reports
});
// Lazy promises
lintDirectory(path).then(function (readdirPromise) {
readdirPromise.then(function (readFileMapPromise) {
readFileMapPromise.then(function (lintMapPromise) {
lintMapPromise.then(function (reports) {
console.log(reports); // [...] array of lint reports
});
});
})
}); |
Indeed, I like the way @medikoo phrases it. There's---practically speaking---nothing wrong with being a monad on the category of non-thenables. |
Excellent! |
I wonder if #103 factors into this in any way. It allows a promise to force the library not to unwrap it, but I can't see any way that it can then also fulfill with a value, rather than itself. I think the idea of them being monads on the category of non-thenables is really nice. I hope that someone will create a promise library with an
As such, none of the current libraries see any value to unwrapping just a single level. The spec can therefore remain as is currently proposed and recursively unwrap thenables. If anyone has anything significantly new to add, I will re-open the issue so the discussion can continue. Otherwise I now consider this closed. |
@ForbesLindesay writes
Q fits into both of the categories you list. Q far references are indeed fulfilled "Promises for remote values". Since they are fulfilled, they need to invoke their .then's success callback. But their fulfillment value is remote, so the most fulfilled representative that can provide locally are themselves, so "they always resolve to themselves". But a Q far reference is not fulfilled by itself, it is fulfilled by the remote value. In no case is a Q promise fulfilled by a promise, even though it looks like that locally through the .then API. This is perhaps clearer when looking at the .there API http://wiki.ecmascript.org/doku.php?id=strawman:concurrency#there , where the success callback is always called with a non-promise. |
@erights I would actually suggest that Q is essentially 2 libraries that happen to be published as one npm module and share a lot of source code. It creates 2 types of promises and each promise fits into one of the two categories, but not both. The fulfillment value of a promise is determined using the following: function logFulfillmentValue(promise) {
promise.then(function (fulfillmentValue) {
console.dir(fulfillmentValue);
});
} based on that, fairly intuitive definition (which I think aligns well with the spec), your far references fulfill to themesleves. I think the wording you've used of
is a little confusing, the promise doesn't do the fulfilling, it has the fulfilling done to it, but it is fulfilled with itself as the fulfillment value. At any rate, this is off topic here, I'm happy to discuss it elsewhere some time. |
I'd like to propose we re-visit the discussion surrounding the recursive nature of the new resolution algorithm. I understand that this is in-line with how many promise libraries currently operate, but I think we should consider whether it has to be that way as I have now managed to find some fairly compelling use cases of promises for promises and it does offer great gains in compatibility with monad systems.
I realize this is a contentious issue and these threads can quickly get very long and full of circular arguments. To help with this I have created 2 gists:
I remain personally fairly on the fence over this and have attempted to make both arguments as strong as I can.
For the first 36 Hours (until 2013-04-17T15:00:00Z) I would like people to refrain from commenting in this issue unless said comments are completely neutral. I would also like you to refrain from posting any rebuttals in either gist. Please read both gists, and add any arguments in favour of promises for promises to the For gist as comments and any arguments against promises for promises to the Against gist as comments.
I will attempt to merge any arguments I see in the comments into the main document, and will then delete those comments. The aim is to end that 36 hour period with two arguments that are both concise enough for everyone to read more or less in full and as solid/persuasive as they can be.
I would also like to request that everyone reads (or re-reads if you read them a while ago and need to refresh your memory) both of the following 2 documents:
The text was updated successfully, but these errors were encountered: