- From: Tab Atkins Jr. <jackalmage@gmail.com>
- Date: Fri, 3 May 2013 08:28:53 -0700
- To: Domenic Denicola <domenic@domenicdenicola.com>
- Cc: Jonas Sicking <jonas@sicking.cc>, "Mark S. Miller" <erights@google.com>, "public-script-coord@w3.org" <public-script-coord@w3.org>
On Thu, May 2, 2013 at 5:38 PM, Domenic Denicola <domenic@domenicdenicola.com> wrote: > So, to preface, this entire reply is going to talk about promises-for-promises, not thenable assimilation. Sorry about taking over your well-intentioned thread on thenable assimilation, but I think there have been some points made here that need to be answered. Since there was apparently confusion on my part about how much promises-for-promises was resolved (though, I maintain, due partially to confusing wording ^_^), I'm fine with this. > From: Tab Atkins Jr. [mailto:jackalmage@gmail.com] > >> Can you elaborate on the complexity? Based on the Futures spec, it seems to be pretty simple. > > Not the spec complexity of course; fulfill is simpler than resolve in that sense. The complexity it introduces into the ecosystem, by allowing promises for promises to exist and gum up the works. What I've tried to argue, apparently convincingly to many, is that it's hard to accidentally get a nested promise. You can't do it by accident. Thus, I don't see the threat to the "ecosystem". You start with a promise. In the .then() callbacks, you return either a value, or another promise. Regardless, you end up with a single-layer promise. The only way to end up with a nested promise is to *explicitly* double-wrap it, in which case you either know what you're doing (and don't want automatic unwrapping), or you have no idea what you're doing, and an error is much better than papering over the author's confusion. >> This is, generically, the ability to compose Futures without having to worry about what's inside of them. You've come up with one example. > > I disagree. This is about a two-stage computation, which should be explicitly separated as such if desired (and it's not even clear it is desired). For example, instead of storing a FunctionFuture directly, the API could store a `{ functionToCompile }` object, so you do > > ```js > indexedDB.get('storedFunction').then(function (storedFunction) { > storedFunction.functionToCompile.then(function (compiledFunction) { > // use compiledFunction here. > }); > }); > ``` I fundamentally don't understand this suggestion. This is creating an object whose sole reason for existence is to hold a promise. It's creating a nested promise with a single level of useless indirection in the middle. Why do that? It just makes busy-work, and makes it harder to fully flatten when you *do* want to just say "gimme the value in the center". >> The community of functional programmers has shown that, in general, this kind of thing is very often useful, and is captured in the abstract pattern of "monad". >> >> Mark has argued against nesting, based on his experience with E, where promises are featureless wrappers that disappear as soon as they're no longer needed. > > This again. We have a few problems here: > > 1. First, there's a double-standard here: you are arguing based on your experience with functional, strongly-typed languages, and telling us that applies, while at the same time telling us that Mark's experience with E doesn't apply. I'm saying that because at least part of his experience *doesn't* apply. Nesting promises fundamentally doesn't make sense in E, because as soon as the outer promise fulfills, *it disappears*. There's no promise left to nest! As far as the author can tell, it's now and was always the inner promise, unless of course that has fulfilled as well, in which case it's just a plain value. That sort of mechanic makes sense, but JS won't do it. Thus, we need to make sure that our mental models include a promise that sticks around after fulfillment, and that we take full advantage of that situation. We can afford to make promises more than just featureless references. Hopefully this shows why this isn't just a "double standard" - I'm trying to point out the real differences here. > 2. You assume Mark is arguing based on experiences with E. That seems false: both Mark and the Promises/A+ community have worked extensively with *JavaScript* promises, and are telling you that promises-for-promises are bad news. Based on your previous comments, it *appears* that most of what you have a problem with are *accidental* nested promises, caused by mutually non-communicating promise libraries. This is a *completely* different subject than purposely nested promises in a single library. I agree that accidentally nested promises are bad. I also argue, as I did above and in several other places, that it's very hard to accidentally nest native promises. > 3. It would be helpful if you established your credibility in the area of functional, strongly-typed languages, so that we could be sure you were speaking from experience instead of just repeating what has appeared elsewhere. Just as a baseline, mind; I'm not accusing you of this, but just saying it would be helpful to set the stage for discussion so that we know where everyone is coming from. I would hope that my arguing so far shows that I know what I'm talking about. If I must show my papers, though, I've been programming in Lisp since I was in high school, and wrote a lot of monadic code myself as I learned about it in Haskell (in addition to some fun arrow-based stuff, which seemed *so* much simpler when I actually implemented it, rather than trying to understand the category-theory descriptions). Note that Lisp is a dynamic strongly-typed language. Monads work just fine - you don't get some type inference niceties for free, but that is only a minor convenience. JS is identical to Lisp in this regard (and in many other ways). > 4. I contend that solutions that work in strongly-typed, functional languages do not transfer well to JavaScript. There are a myriad of technical issues here we could discuss, from the existence of `throw`, to the possibility of returning different types, to side effects, but let's instead focus on the actual experimental evidence. Namely, every time I ask for an example of these hypothetical monad libraries in JS being used extensively, I get nothing but echoes. This indicates nobody is using such libraries in JavaScript extensively to the extent they are using promises, so perhaps we should be thinking about how promises work instead of how monads work? > > It might be the case that we are simply too early, and there is a JS monad renaissance in the wings. I sincerely doubt it, but even if that did happen, we'd need to wait at least a few years to see the kind of widespread use of monads as we do of promises. Maybe then we'd add a `flatMap` function to whatever promise standard exists at the time, to pave the existing cowpath as we are doing with `then` and promises. > > In short, you keep trying to conflate promises and monads, but it really just comes across as you trying to piggyback a related-but-different concept on top of the one we are all discussing. Like some sort of congressional rider bill, really. Let it go? None of those issues matter in the slightest to the usefulness of the monad abstraction. Monad is just a typeclass, identical to "iterable" or "array-like" or "mappable" or a host of other abstractions that JS classes use, either explicitly or implicitly. Don't fetishize "monad" as something magical and only applicable to crazy languages like Haskell - it's just a funky name for the concept of chaining a value in some context, a trivial (though extremely useful) generalization of the "functor"/"mappable" abstraction that JS already has for all of its relevant built-ins (all the container types). What I've been *trying* to do this entire time is find out *why* native nested promises are harmful. Every. Single. Person. who's tried to tell me that nested promises are harmful so far, has ended up realizing that they were talking about assimilation, or non-JS promises with weird language semantics, or programmer bugs that would benefit from causing an error, rather than having the excessive-wrapping bug ironed over. Seriously, please read the thread <http://lists.w3.org/Archives/Public/public-script-coord/2013AprJun/0279.html> and tell me if a case was missed! I kept digging for them, and nobody has been able to give me one so far. :/ > 5. You have never address the problem of the identity function. If you cannot create promises for promises, then `var p2 = p.then(x => x)` is always an identity transformation; `p2` is always fulfilled with the same value as `p`. However, given `var f = Future.accept(Future.accept(5))`, you have that `var f2 = f.then(x => x)` is no longer an identity transformation; `f2` is fulfilled with `5`, whereas `f` is fulfilled with `Future.accept(5)`. To me this is an extremely clear example of how you are trying to port over semantics from monads to promises that do not fit with the promise framework, and more generally trying to port over semantics from strongly-typed languages to JavaScript, where you can't enforce the return type of the `onFulfilled` callback. I'm confused - what was I supposed to address about identify functions? If I missed a direct question from you in an earlier thread, I apologize. The identity function for monad callbacks is their lifting operator. In this case, that's Future.accept(). The normal identity function (x=>x) is useful for mappables, which promises *kinda* are, but through magic that interferes with unconditional mapping, as you demonstrate. > 6. Finally, related to #5, having these unfortunate entities in the system requires guarding against them. I'd encourage you to take a look at Mark's latest smart contracts paper, and consider what would happen to the workings therein if promises-for-promises started flowing through the system. The identity function breakage certainly, but I would imagine much more. I did. Mark brought up an example in his contracts paper in a much earlier thread, and as far as I could figure out, it had nothing to do with nested promises - it was about a value which might be a plain value or a promise, and wanting to deal with as simply as possible. In E, that's just a matter of calling Q() with the value. In the Futures spec, it's exactly the same - call Future.resolve() on the value. Again, an *accidental* nested promise would indeed be annoying and bad. But I challenge you to create one! "Accidental nested promises" is a boogeyman that is very rare in reality, if you have a properly-functioning abstraction (which we do). >> It occurs to me now that Domenic may have been doing the same "nested promises can't exist, so of course you (trivially) don't need recursive flattening for them" thing that Mark did earlier in this thread, in which case >_<. > > Somewhat. I find the issue of what happens to nested promises very uninteresting, since they will hopefully not exist. I admit that if the monad camp does push them through the W3C, then we'll have to talk about their semantics, but I'd rather bite that bullet when we get shot with it (or something). Okay. I'd appreciate if, when your answer to a question is because the question itself is moot, that you say so, so as to avoid confusion with people who don't believe it's moot. ^_^ ~TJ
Received on Friday, 3 May 2013 15:29:40 UTC