Discussion:
A Challenge Problem for Promise Designers (was: Re: Futures)
Mark Miller
2013-04-25 15:57:38 UTC
Permalink
I think we see a correlation -- not a 1.0 correlation, but something. Those
who've actually used promise libraries with this flattening property find
it pleasant. Those who come from either a statically typed or monadic
perspective, or have had no experience with flattening promises, generally
think they shouldn't flatten. Even if this correlation is there and even if
it indicates what it seems to indicate, by itself it is hard to use as a
basis for argument. It would merely shut out of the argument people without
that experience, which is not what I intend and would not help us move
forward.

At <http://research.google.com/pubs/pub40673.html>, Tom, Bill Tulloh, and I
wrote a paper where we demonstrate with a *very small* example, how
beautiful programming with promises can be. This example is small enough
that those who advocate different rules can try rewriting it using
promises-as-they-wish-them-to-be and compare.


Even this exercise though would leave out the history of the code in the
paper, and so not illuminate the refactoring point David just made. A very
concrete example of the effect David is talking about is seen in Figure 1:
The Mint Maker, on page 13. In the current paper, its deposit method reads:

deposit: (amount, srcP) =>
Q(srcP).then(src => {
Nat(balance + amount);
m.get(src)(Nat(amount));
balance += amount;
})

where the body of the deposit method doesn't fire until the promise for the
source purse, srcP, resolves to a source purse. Previously, it read

deposit: (amount, src) => {
Nat(balance + amount);
m.get(src)(Nat(amount));
balance += amount;
}

requiring its clients to pass in a resolved source purse, rather than a
promise for one. Some of the clients already had a resolved source purse to
pass, so for these it was no problem, they just passed it. On page 12:

var ackP = paymentP ! deposit(10, myPurse);

Others had received a promise for a payment purse from elsewhere, that they
intended to use as a source purse. They had to do a .then on this payment
purse and then send .deposit only from inside the body of the then. The buy
method of page 13 used to be:

buy: (desc, paymentP) =>
Q(paymentP).then(payment => {
// do whatever with desc, look up $10 price
return (myPurse ! deposit(10, payment)).then(_ => good);
})

This also came up in several places in the escrow exchange agent in Figure
2. The deposit method itself, having had no explicit return, would
terminate either by returning undefined, indicating success, or throwing,
indicating failure. The promise for the eventual result of the deposit
method, such as ackP above, would thereby be a promise-for-undefined. It
would eventually either fulfill successfully with undefined or be rejected
with the thrown error as the reason.

The refactoring of putting the "Q(srcP).then" in the deposit method
unburdened all clients such as the buy method above from doing this
postponement themselves. The new buy method on page 13 now reads:

buy: (desc, paymentP) => {
// do whatever with desc, look up $10 price
return (myPurse ! deposit(10, paymentP)).then(_ => good);
}

The old deposit method returned undefined or threw. The new deposit method
itself returns a promise-for-undefined that either fulfills to undefined or
rejects. However, in both cases, the promise for what the deposit method
will return remains the same, and so the buy method above did not become
burdened with having to do a doubly nested then in order to find out
whether the deposit succeeded. In addition, our first example line of
client code

var ackP = paymentP ! deposit(10, myPurse);

did not have to change at all. The Q(srcP).then at the beginning of the
deposit method will turn a purse into a promise for a purse, but will not
turn a promise for a purse into a promise for a promise for a purse. The
ackP remains a one-level promise whose fulfillment or rejection indicates
whether the deposit succeeds or fails.

Call this refactoring "shifting the burden of postponement".


I hope this gives some sense about why those who've experienced such
patterns like them. And I hope this provides a concrete and meaningful
challenge to those who think promises should work otherwise.



On Thu, Apr 25, 2013 at 2:22 AM, David Bruant <bruant.d at gmail.com> wrote:

> Le 24/04/2013 19:41, Andreas Rossberg a ?crit :
>
>> On 24 April 2013 19:20, Mark S. Miller <erights at google.com> wrote:
>>
>>> On Wed, Apr 24, 2013 at 10:14 AM, Tab Atkins Jr. <jackalmage at gmail.com>
>>> wrote:
>>>
>>>> Q and similar libraries don't actually assume that a Future<Future<x>>
>>>> is a Future<x>.
>>>>
>>> Yes it does. Except of course that we call these "promises". Please see
>>> the
>>> extensive discussions on the Promises/A+ site about why this flattening
>>> behavior is important.
>>>
>> That strikes me as a very odd design decision, since it would seem to
>> violate all sorts of structural and equational invariants.
>>
> From a developer perspective, my experience using promises is that it's an
> extremely convenient property. Basically, if you can only have x and
> Future<x>, but never Future<Future<x>>, you never have to worry about the
> resolved value. You know it's always a non-Future value. And that's what
> you want. When you call getQueryResult(query).then(**function(result){...}),
> you always want result to be something you can play with, not a promise.
> Unless you're writing the promise infrastructure, you don't want to have to
> worry about that.
>
> If Future<Future<x>> can exist, then you'll have to write this boilerplate
> code in a lot of places:
> f.then(function res(v){
> if(Future.isFuture(v)){
> v.then(res);
> }
> else{
> // actual work with the resolved value.
> }
> })
>
> The promise library taking care of this boilerplate for you is a good
> property in my opinion. It also helps making chaining more usable and ease
> refactoring:
>
> var p2 = p.then(function(x){
> return somethingWith(x);
> })
>
> Whether somethingWith(x) returns a promise or non-promise value, you know
> that in p2.then(), you'll be dealing with a non-promise value. If, for
> whatever reason, you change somethingWith(x); to return a promise instead
> of a non-promise (or vice-versa), none of your code (beyond the
> somethingWith body) needs to be changed (assuming, you're only returning
> the value as it's the case here).
> I've had only once to change the signature of a function from a
> non-promise to a promise and that property was really useful.
>
> As soon as you have a decent code base with a lot of functions returning
> and playing with promises, it's nice to not have to worry about "a promise
> for a promise" and have the promise infrastructure doing the flattening
> work for you.
>
> David
>
> ______________________________**_________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/**listinfo/es-discuss<https://mail.mozilla.org/listinfo/es-discuss>
>



--
Text by me above is hereby placed in the public domain

Cheers,
--MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130425/a3637570/attachment-0001.html>
Claus Reinke
2013-04-25 16:37:36 UTC
Permalink
> I think we see a correlation -- not a 1.0 correlation, but something. Those
> who've actually used promise libraries with this flattening property find
> it pleasant. Those who come from either a statically typed or monadic
> perspective, or have had no experience with flattening promises, generally
> think they shouldn't flatten.

I think the dispute could be settled easily:

- flattening 'then' is a convenience
- non-flattening 'then' is necessary for promises being thenables
(in the monad-inspired JS patterns sense)

Why not have both? Non-flattening 'then' for generic thenable
coding, and a convenience method 'then_' for 'then'+flattening.

That way, coders can document whether they expect convenience
or standard thenable behavior. And we can have convenience for
Promise coding without ruining Promises for more general thenable
coding patterns.

Claus
Claus Reinke
2013-04-25 22:21:28 UTC
Permalink
I'm still wading through the various issue tracker threads, but only two
concrete rationales for flattening nested Promises have emerged so far:

1 "library author doesn't want nested Promises."
2 crossing Promise library boundaries can create unwanted nesting

There is little to be said about 1, only that those library authors still
have a choice: add a separate recursive flattening operation and keep
the thenable operations unharmed, or give up on Promises being
thenables in the monad-inspired JS patterns sense (and hence give
up on profiting from generic thenable library code).

The second point is somewhat more interesting, as it stems from yet
another convenience-driven thenable deviation: if a then-callback does
not return a Promise, its result is implicitly lifted into a Promise; the
unwanted nesting apparently comes from different libs not recognizing
each others promises, mistaking foreign promises for values, and lifting
them into their own promises. Recursive flattening (assimilation) is
then intended as a countermeasure to recursive lifting of foreign
promises.

It will come as no surprise that I think implicit lifting is just as mistaken
and recursive flattening;-) Both should be moved to explicit convenience
methods, leaving the generic 'then'/'of' interface with the properties
needed for generic thenable library code.

Claus

>> I think we see a correlation -- not a 1.0 correlation, but something. Those
>> who've actually used promise libraries with this flattening property find
>> it pleasant. Those who come from either a statically typed or monadic
>> perspective, or have had no experience with flattening promises, generally
>> think they shouldn't flatten.
>
> I think the dispute could be settled easily:
>
> - flattening 'then' is a convenience
> - non-flattening 'then' is necessary for promises being thenables
> (in the monad-inspired JS patterns sense)
>
> Why not have both? Non-flattening 'then' for generic thenable
> coding, and a convenience method 'then_' for 'then'+flattening.
>
> That way, coders can document whether they expect convenience
> or standard thenable behavior. And we can have convenience for
> Promise coding without ruining Promises for more general thenable
> coding patterns.
>
> Claus
>
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
Domenic Denicola
2013-04-25 22:31:13 UTC
Permalink
Can you point to any code in wide use that makes use of this "thenables = monads" idea you seem to be implicitly assuming? Perhaps some of this "generic thenable library code"? I have never seen such code, whereas the use of "thenable" to mean "object with a then method, which we will try to treat as a promise" as in Promises/A+ seems widely deployed throughout libraries that are used by thousands of people judging by GitHub stars alone.

Thus I would say it's not promise libraries that are "harming the thenable operations," but perhaps some minority libraries who have misinterpreted what it means to be a thenable.
________________________________
From: Claus Reinke<mailto:claus.reinke at talk21.com>
Sent: ?4/?25/?2013 18:21
To: Mark Miller<mailto:erights at gmail.com>; David Bruant<mailto:bruant.d at gmail.com>
Cc: Mark S. Miller<mailto:erights at google.com>; es-discuss<mailto:es-discuss at mozilla.org>
Subject: Re: A Challenge Problem for Promise Designers (was: Re: Futures)

I'm still wading through the various issue tracker threads, but only two
concrete rationales for flattening nested Promises have emerged so far:

1 "library author doesn't want nested Promises."
2 crossing Promise library boundaries can create unwanted nesting

There is little to be said about 1, only that those library authors still
have a choice: add a separate recursive flattening operation and keep
the thenable operations unharmed, or give up on Promises being
thenables in the monad-inspired JS patterns sense (and hence give
up on profiting from generic thenable library code).

The second point is somewhat more interesting, as it stems from yet
another convenience-driven thenable deviation: if a then-callback does
not return a Promise, its result is implicitly lifted into a Promise; the
unwanted nesting apparently comes from different libs not recognizing
each others promises, mistaking foreign promises for values, and lifting
them into their own promises. Recursive flattening (assimilation) is
then intended as a countermeasure to recursive lifting of foreign
promises.

It will come as no surprise that I think implicit lifting is just as mistaken
and recursive flattening;-) Both should be moved to explicit convenience
methods, leaving the generic 'then'/'of' interface with the properties
needed for generic thenable library code.

Claus

>> I think we see a correlation -- not a 1.0 correlation, but something. Those
>> who've actually used promise libraries with this flattening property find
>> it pleasant. Those who come from either a statically typed or monadic
>> perspective, or have had no experience with flattening promises, generally
>> think they shouldn't flatten.
>
> I think the dispute could be settled easily:
>
> - flattening 'then' is a convenience
> - non-flattening 'then' is necessary for promises being thenables
> (in the monad-inspired JS patterns sense)
>
> Why not have both? Non-flattening 'then' for generic thenable
> coding, and a convenience method 'then_' for 'then'+flattening.
>
> That way, coders can document whether they expect convenience
> or standard thenable behavior. And we can have convenience for
> Promise coding without ruining Promises for more general thenable
> coding patterns.
>
> Claus
>
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
_______________________________________________
es-discuss mailing list
es-discuss at mozilla.org
https://mail.mozilla.org/listinfo/es-discuss

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130425/e567c16a/attachment-0001.html>
Dean Tribble
2013-04-25 23:30:24 UTC
Permalink
I've built multiple large systems using promises. A fundamental distinction
that must be clear to the client of a function is whether the function
"goes async": does it return a result that can be used synchronously or
will the result only be available in a later turn. The .Net async libraries
require the async keyword precisely to surface that in the signature of the
function; i.e., it is a breaking change to a function to go from returning
a ground result vs. a promise for a result. The same is basically isn't
true for returning a promise that will only be resolved after several turns.

For example: I have a helper function to get the size of contents at the
other end of a URL. Since that requires IO, it must return a Promise<int>.

size: (url) => {
return url.read().then(contents => contents.length)
}

This is obviously an expensive way to do it, and later when I get wired
into some nice web caching abstraction, I discover that the cache has a
similar operation, but with a smarter implementation; e.g., that can get
the answer back by looking at content-length in the header or file length
in the cache. That operation of course may require IO so it returns a
promise as well. Should the client type be different just because the
implementation uses any of several perfectly reasonable approaches for the
implementation.

size: (url) => {
return _cacheService.getLength(url)
}

If in order to not change the signature, I have to "then" the result, it
leads to

size: (url) => {
return _cacheService.getLength(url).then(length => length)
}

This just adds allocation and scheduling overhead for the useless then
block, precludes (huge) tail return optimization, and clutters the code.
This also leads to a depth of nesting types which is comparable to the
function nesting depth (i.e., if x calls y calls z do I have
promise<promise<promise<Z>>>?), which is overwhelming both to the type
checkers and to the programmers trying to reason about the code. the client
invoked an operation that will eventually produce the integer they need.

There is also a relation between flattening and error propagation: consider
that returning a broken promise is analogous to throwing an exception in
languages with exceptions. In the above code, if the cache service fails
(e..g, the URL is bogus), the result from the cache service will
(eventually) be a rejected promise. Should the answer from the size
operation be a fulfilled promise for a failed result? That would extremely
painful in practice. Adding a layer of promise at each level is equivalent
in sequential to requiring that every call site catch exceptions at that
site (and perhaps deliberately propagate them). While various systems have
attempted that, they generally have failed the usability test. It certainly
seems not well-suited to the JS environment.

There are a few cases that may require "promise<promise<T>>". Most can be
more clearly expresses with an intermediate type. For example, in an
enterprise security management system, the service manager returned a
promise for a (remote) authorization service, but the authorization service
might have been broken. Instead of returning a
Promise<Promise<AuthorizationService>>, it returned
Promise<AuthorizationConnection> where AuthorizationConnection had a member
"service" that returned a Promise<AuthorizationService>. When you deal
with higher level abstractions in a parameterized type system like C#s,
however, you may end up with APIs that want to work across any T, including
promises. If the abstractions internally use promises, then they may well
end up with Promise<T> where T : Promise<U> or some such. Those are very
rare in practice, and can typically make use of operators (e.g., like Q) to
limit their type nesting depth.

On Thu, Apr 25, 2013 at 3:31 PM, Domenic Denicola <
domenic at domenicdenicola.com> wrote:

> Can you point to any code in wide use that makes use of this "thenables
> = monads" idea you seem to be implicitly assuming? Perhaps some of this
> "generic thenable library code"? I have never seen such code, whereas the
> use of "thenable" to mean "object with a then method, which we will try to
> treat as a promise" as in Promises/A+ seems widely deployed throughout
> libraries that are used by thousands of people judging by GitHub stars
> alone.
>
> Thus I would say it's not promise libraries that are "harming the thenable
> operations," but perhaps some minority libraries who have misinterpreted
> what it means to be a thenable.
> ------------------------------
> From: Claus Reinke <claus.reinke at talk21.com>
> Sent: 4/25/2013 18:21
> To: Mark Miller <erights at gmail.com>; David Bruant <bruant.d at gmail.com>
> Cc: Mark S. Miller <erights at google.com>; es-discuss<es-discuss at mozilla.org>
> Subject: Re: A Challenge Problem for Promise Designers (was: Re: Futures)
>
> I'm still wading through the various issue tracker threads, but only two
> concrete rationales for flattening nested Promises have emerged so far:
>
> 1 "library author doesn't want nested Promises."
> 2 crossing Promise library boundaries can create unwanted nesting
>
> There is little to be said about 1, only that those library authors still
> have a choice: add a separate recursive flattening operation and keep
> the thenable operations unharmed, or give up on Promises being
> thenables in the monad-inspired JS patterns sense (and hence give
> up on profiting from generic thenable library code).
>
> The second point is somewhat more interesting, as it stems from yet
> another convenience-driven thenable deviation: if a then-callback does
> not return a Promise, its result is implicitly lifted into a Promise; the
> unwanted nesting apparently comes from different libs not recognizing
> each others promises, mistaking foreign promises for values, and lifting
> them into their own promises. Recursive flattening (assimilation) is
> then intended as a countermeasure to recursive lifting of foreign
> promises.
>
> It will come as no surprise that I think implicit lifting is just as
> mistaken
> and recursive flattening;-) Both should be moved to explicit convenience
> methods, leaving the generic 'then'/'of' interface with the properties
> needed for generic thenable library code.
>
> Claus
>
> >> I think we see a correlation -- not a 1.0 correlation, but something.
> Those
> >> who've actually used promise libraries with this flattening property
> find
> >> it pleasant. Those who come from either a statically typed or monadic
> >> perspective, or have had no experience with flattening promises,
> generally
> >> think they shouldn't flatten.
> >
> > I think the dispute could be settled easily:
> >
> > - flattening 'then' is a convenience
> > - non-flattening 'then' is necessary for promises being thenables
> > (in the monad-inspired JS patterns sense)
> >
> > Why not have both? Non-flattening 'then' for generic thenable
> > coding, and a convenience method 'then_' for 'then'+flattening.
> >
> > That way, coders can document whether they expect convenience
> > or standard thenable behavior. And we can have convenience for
> > Promise coding without ruining Promises for more general thenable
> > coding patterns.
> >
> > Claus
> >
> > _______________________________________________
> > es-discuss mailing list
> > es-discuss at mozilla.org
> > https://mail.mozilla.org/listinfo/es-discuss
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
>
>
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130425/66903435/attachment.html>
Tab Atkins Jr.
2013-04-26 00:08:51 UTC
Permalink
On Thu, Apr 25, 2013 at 4:30 PM, Dean Tribble <tribble at e-dean.com> wrote:
> I've built multiple large systems using promises. A fundamental distinction
> that must be clear to the client of a function is whether the function "goes
> async": does it return a result that can be used synchronously or will the
> result only be available in a later turn. The .Net async libraries require
> the async keyword precisely to surface that in the signature of the
> function; i.e., it is a breaking change to a function to go from returning a
> ground result vs. a promise for a result. The same is basically isn't true
> for returning a promise that will only be resolved after several turns.
>
> For example: I have a helper function to get the size of contents at the
> other end of a URL. Since that requires IO, it must return a Promise<int>.
>
> size: (url) => {
> return url.read().then(contents => contents.length)
> }
>
> This is obviously an expensive way to do it, and later when I get wired into
> some nice web caching abstraction, I discover that the cache has a similar
> operation, but with a smarter implementation; e.g., that can get the answer
> back by looking at content-length in the header or file length in the cache.
> That operation of course may require IO so it returns a promise as well.
> Should the client type be different just because the implementation uses any
> of several perfectly reasonable approaches for the implementation.
>
> size: (url) => {
> return _cacheService.getLength(url)
> }
>
> If in order to not change the signature, I have to "then" the result, it
> leads to
>
> size: (url) => {
> return _cacheService.getLength(url).then(length => length)
> }
>
> This just adds allocation and scheduling overhead for the useless then
> block, precludes (huge) tail return optimization, and clutters the code.

I don't understand this example. In the last one, if the return value
of _cacheService.getLength(url) is a future already, why do you need
to call .then() on it? Are you unsure of whether getLength() returns
a Future<length> or a Future<Future<length>>? If so, getLength() is
terribly broken, and should be flattening stuff by itself to return a
consistent type. We don't need to engineer around that kind of
application design mistake at the language level.

> This also leads to a depth of nesting types which is comparable to the
> function nesting depth (i.e., if x calls y calls z do I have
> promise<promise<promise<Z>>>?), which is overwhelming both to the type
> checkers and to the programmers trying to reason about the code. the client
> invoked an operation that will eventually produce the integer they need.
>
> There is also a relation between flattening and error propagation: consider
> that returning a broken promise is analogous to throwing an exception in
> languages with exceptions. In the above code, if the cache service fails
> (e..g, the URL is bogus), the result from the cache service will
> (eventually) be a rejected promise. Should the answer from the size
> operation be a fulfilled promise for a failed result? That would extremely
> painful in practice. Adding a layer of promise at each level is equivalent
> in sequential to requiring that every call site catch exceptions at that
> site (and perhaps deliberately propagate them). While various systems have
> attempted that, they generally have failed the usability test. It certainly
> seems not well-suited to the JS environment.

I don't understand this either, probably because I don't understand
the reason for the .then() in the earlier example. If
cacheService.getLength() returns a future, then you don't need to do
anything special in the size() function - just return the future that
it returns. It sounds like you're nesting values in futures for the
hell of it, which of course is problematic. Hiding the application's
mistakes by auto-flattening isn't a good idea.

If you don't know whether a given function will return a bare value or
a future, but you need to return a future, there's always
Future.resolve(), which has the same semantics "unwrap 0 or 1 layers"
semantics as Future#then.

I can't quite understand the point of the examples given here, so I
may be misinterpreting them uncharitably. Could you elaborate on
them, particularly on the cacheService example? What does
cacheService.getLength() return, and what does size() need to return?

~TJ
Dean Tribble
2013-04-26 01:03:16 UTC
Permalink
Hmm. I agree that the example code isn't relevant to JavaScript. For
background, the last time issues this came up for me was in the context of
a language keyword (which had other interesting but unrelated trade offs),
where it really did impose that interaction (call sites had to declare that
the type was a promise, and handle that, even though they were then
returning promises). I'm glad we agree that needing to "then" in the
tail-call case would be silly for a promise library. So what's an example
that motivates you to want to build a tower of promise types? The main one
I know of is the implementation (not use of) higher-order collection
constructs that use promises internally (e.g., the implementation of map
and reduce for an async, batching, flow-controlled stream of Promise<T>).
That kind of rare example can have more advanced hooks (like Q).

On Thu, Apr 25, 2013 at 5:08 PM, Tab Atkins Jr. <jackalmage at gmail.com>wrote:

> ... If cacheService.getLength() returns a future, then you don't need to do
> anything special in the size() function - just return the future that
> it returns. It sounds like you're nesting values in futures for the
> hell of it, which of course is problematic. Hiding the application's
> mistakes by auto-flattening isn't a good idea....
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130425/2b842c1e/attachment.html>
Tab Atkins Jr.
2013-04-26 01:39:26 UTC
Permalink
On Thu, Apr 25, 2013 at 6:03 PM, Dean Tribble <tribble at e-dean.com> wrote:
> So what's an example
> that motivates you to want to build a tower of promise types? The main one
> I know of is the implementation (not use of) higher-order collection
> constructs that use promises internally (e.g., the implementation of map and
> reduce for an async, batching, flow-controlled stream of Promise<T>). That
> kind of rare example can have more advanced hooks (like Q).

I think it's more important to see examples of why you want to
*flatten* them automatically. Having "towers of promises" keeps us
consistent with Futures-as-monads, which is useful from a theoreticaly
standpoint. This is similar, from an algebraic standpoint, to
allowing arrays of arrays, or sets of sets, or any other monadic
context doubled up.

~TJ
David Bruant
2013-04-26 08:54:04 UTC
Permalink
Le 26/04/2013 03:39, Tab Atkins Jr. a ?crit :
> On Thu, Apr 25, 2013 at 6:03 PM, Dean Tribble <tribble at e-dean.com> wrote:
>> So what's an example
>> that motivates you to want to build a tower of promise types? The main one
>> I know of is the implementation (not use of) higher-order collection
>> constructs that use promises internally (e.g., the implementation of map and
>> reduce for an async, batching, flow-controlled stream of Promise<T>). That
>> kind of rare example can have more advanced hooks (like Q).
> I think it's more important to see examples of why you want to
> *flatten* them automatically. Having "towers of promises" keeps us
> consistent with Futures-as-monads, which is useful from a theoreticaly
> standpoint.
The Priority of Constituencies [1] asks us to be remain careful about
theoretical standpoints. How does the theoretical part translates into
helping users? authors (more than what I described at [2] which is
derived from my own experience)? implementors? specifiers?
I'm not saying the theoretical benefits don't exist, but I'd like to see
how they translate in concretely improving my life as a developer using
promises. I've explained the benefits I see for flattening from the dev
point of view, I'd like to see the equivalent.

> This is similar, from an algebraic standpoint, to
> allowing arrays of arrays, or sets of sets, or any other monadic
> context doubled up.
Arrays of arrays make sense as a data structure. Promise of promise does
*in theory*, but as Kevin Smith said, in practice, promises are
featureless values; the only thing you care about is that an operation
is async and the actual non-promise eventual value that you'll get.
Given this practical use of promises, why bother with Future<Future<T>>?
I don't mind if they exist, but as a developer, I'd like
Future<Future<T>> to be hidden so that I don't have to deal with them. I
want all APIs I interact with to make me interact with promises and
non-promises values and that's it. I don't care about the rest. Hide
Future<Future<T>> under the carpet, please.
If super-experts want to play with Future<Future<T>>, give them, but
don't make that the default way to interact with promises. (as a side
note, Mark Miller and Domenic Denicola are some of the most experts I
know and they're also asking for flattening. It sounds strong enough of
a signal for me to give on non-flattening; if they had any use, they
would have expressed it).

David

[1] http://www.w3.org/TR/html-design-principles/#priority-of-constituencies
[2] https://mail.mozilla.org/pipermail/es-discuss/2013-April/030192.html
Andreas Rossberg
2013-04-26 09:43:35 UTC
Permalink
On 26 April 2013 10:54, David Bruant <bruant.d at gmail.com> wrote:
> The Priority of Constituencies [1] asks us to be remain careful about
> theoretical standpoints. How does the theoretical part translates into
> helping users? authors (more than what I described at [2] which is derived
> from my own experience)? implementors? specifiers?
> I'm not saying the theoretical benefits don't exist, but I'd like to see how
> they translate in concretely improving my life as a developer using
> promises. I've explained the benefits I see for flattening from the dev
> point of view, I'd like to see the equivalent.

The argument is for regularity. On a global scale, regularity helps
the user, while exceptions, at best, are useful only locally -- an
observation that, unfortunately, is difficult to demonstrate with toy
examples. In particular, irregularity and exceptions become a pain
when you start building abstractions, or plug together abstractions.
In other words, regularity is a prerequisite for what some people
(including me) like to call "compositionality".

Flattening for futures/promises is irregular because it means that
certain behaviour reliably exists in all cases _except_ when the input
is itself a future/promise. This may not seem like a big deal if you
use them directly. But now imagine somebody built a bigger generic
abstraction that uses futures/promises only internally. You find that
abstraction useful, and for whatever reason, need to funnel in a value
that happens to be a future. And for no reason apparent to you the
nice abstraction misbehaves in weird ways.

The nasty thing about lack of compositionality is that it typically
only starts to harm after a while, when people start building more and
more complex systems on top of a feature. But at that point, it's
already too late, and you cannot fix the mistake anymore. Instead what
happens is that more and more exceptions get introduced on top, to
work around the problems created by the first one.

The functional programming community has a lot of experience with
building compositional abstractions. And they have learned to value
regularity very high. (And not entirely coincidentally, that also is
the reason why they value type systems, because those force language
and library designers to stay honest about regularity.)

/Andreas
David Bruant
2013-04-26 10:19:28 UTC
Permalink
[adding public-script-coord and Anne]

Le ven. 26 avril 2013 11:43:35 CEST, Andreas Rossberg a ?crit :
> On 26 April 2013 10:54, David Bruant <bruant.d at gmail.com> wrote:
>>
>> The Priority of Constituencies [1] asks us to be remain careful about
>> theoretical standpoints. How does the theoretical part translates into
>> helping users? authors (more than what I described at [2] which is
>> derived
>> from my own experience)? implementors? specifiers?
>> I'm not saying the theoretical benefits don't exist, but I'd like to
>> see how
>> they translate in concretely improving my life as a developer using
>> promises. I've explained the benefits I see for flattening from the dev
>> point of view, I'd like to see the equivalent.
>
> The argument is for regularity. On a global scale, regularity helps
> the user, while exceptions, at best, are useful only locally -- an
> observation that, unfortunately, is difficult to demonstrate with toy
> examples.
I see. To a large extent, it's also very hard to explain benefits to
refactoring. I gave an abstract example, Mark gave a more concrete small
example, but admittedly, none really capture the benefit I have
experience in a medium-sized Node.js application.

> In particular, irregularity and exceptions become a pain
> when you start building abstractions, or plug together abstractions.
> In other words, regularity is a prerequisite for what some people
> (including me) like to call "compositionality".
>
> Flattening for futures/promises is irregular because it means that
> certain behaviour reliably exists in all cases _except_ when the input
> is itself a future/promise. This may not seem like a big deal if you
> use them directly. But now imagine somebody built a bigger generic
> abstraction that uses futures/promises only internally. You find that
> abstraction useful, and for whatever reason, need to funnel in a value
> that happens to be a future.
This last sentence and especially the ambiguous "for whatever reason" is
the heart of the debate I believe. The idea expressed by Kevin Smith of
promises as featureless values is that there should be no reason for you
to funnel a value that happens to be a promise unless it's expected to
be a promise and not a non-promise value.
I have read somewhere (I can't remember where, hopefully MarkM will
confirm or say if I imagined it) that in E, if a variable contains a
promise and this promise is resolved, then the variable unwraps its
value and referencing to the variable suddenly means referencing to the
value. Promises are so deeply embedded in the language (syntax included)
that this unwrapping is seamless.
If I didn't imagine this, it'd be interesting if MarkM could expand on
that as it seems to imply that promises shouldn't be values but
lower-level than that.

Even if he confirms, it probably won't be possible to have something
that embedded in JavaScript, but I think it's an interesting perspective.


More on the idea of "there should be no reason for you to funnel a value
that happens to be a promise", that's obviously unless you're building a
promise library or tools around promises.

One thing I have been convinced by your message is that it seems
necessary to provide the lowest-level non-flattening primitives so that
people build abstractions can that consider promises as values and will
want to know about nested promise types. It should be possible, but
building such abstractions doesn't seem like the 80% use case.
Based on my experience, I remain pretty strong on the fact that
flattening is a sensible default and really has practical advantages to
large-scale development.

So, what about the following:
1) Providing lowest-level non-flattening primitives so that library
authors have fine-grained control and can build their own abstraction
where they can consider promises as values (what is missing in the
current API?)
2) The API to be used by the average devs
(then/catch/done/every/any/etc.) has flattening semantics as this is
what people with the most experience have been advocating for (people
with a different experience are still free to speak up).
This API would just happen to be one possible library that can be built
on top of 1), but one library which semantics has proven to be useful
and popular among devs.

Would that satisfy everyone?

David
Andreas Rossberg
2013-04-26 11:24:23 UTC
Permalink
On 26 April 2013 12:19, David Bruant <bruant.d at gmail.com> wrote:
>> In particular, irregularity and exceptions become a pain
>> when you start building abstractions, or plug together abstractions.
>> In other words, regularity is a prerequisite for what some people
>> (including me) like to call "compositionality".
>>
>> Flattening for futures/promises is irregular because it means that
>> certain behaviour reliably exists in all cases _except_ when the input
>> is itself a future/promise. This may not seem like a big deal if you
>> use them directly. But now imagine somebody built a bigger generic
>> abstraction that uses futures/promises only internally. You find that
>> abstraction useful, and for whatever reason, need to funnel in a value
>> that happens to be a future.
>
> This last sentence and especially the ambiguous "for whatever reason" is the
> heart of the debate I believe. The idea expressed by Kevin Smith of promises
> as featureless values is that there should be no reason for you to funnel a
> value that happens to be a promise unless it's expected to be a promise and
> not a non-promise value.
>
> I have read somewhere (I can't remember where, hopefully MarkM will confirm
> or say if I imagined it) that in E, if a variable contains a promise and
> this promise is resolved, then the variable unwraps its value and
> referencing to the variable suddenly means referencing to the value.
> Promises are so deeply embedded in the language (syntax included) that this
> unwrapping is seamless.
> If I didn't imagine this, it'd be interesting if MarkM could expand on that
> as it seems to imply that promises shouldn't be values but lower-level than
> that.
>
> Even if he confirms, it probably won't be possible to have something that
> embedded in JavaScript, but I think it's an interesting perspective.

I'm not sure if your description of E is accurate -- I'd find that
surprising. It _is_ a perfectly sensible design to have transparent
futures that you can just use in place of the value they eventually
get resolved to (let's call that value their 'content'). In fact, that
is how futures/promises where originally proposed (back in the 70s)
and implemented (e.g. in MultiLisp in 85, Oz in 95, and others later).
However, there are really only two consistent points in the design
space:

1. Either, futures are objects in their own right, with methods and
everything. Then they should be fully compositional, and never be
equated with their content, even after resolved (because that would
change the meaning of a reference at locally unpredictable points in
time).

2. Or, futures are entirely transparent, i.e. can be used everywhere
as a placeholder for their content. In particular, that means that
'f.then' always denotes the 'then' method of the content, not of the
future itself (even when the content value is not yet available).

Unfortunately, option (2) requires that accessing a future that is not
yet resolved has to (implicitly) block until it is (which is what
happens in MultiLisp and friends). That only makes sense with threads,
so I don't see how it can be reconciled with JavaScript.


> One thing I have been convinced by your message is that it seems necessary
> to provide the lowest-level non-flattening primitives so that people build
> abstractions can that consider promises as values and will want to know
> about nested promise types. It should be possible, but building such
> abstractions doesn't seem like the 80% use case.
> Based on my experience, I remain pretty strong on the fact that flattening
> is a sensible default and really has practical advantages to large-scale
> development.
>
> So, what about the following:
> 1) Providing lowest-level non-flattening primitives so that library authors
> have fine-grained control and can build their own abstraction where they can
> consider promises as values (what is missing in the current API?)
> 2) The API to be used by the average devs (then/catch/done/every/any/etc.)
> has flattening semantics as this is what people with the most experience
> have been advocating for (people with a different experience are still free
> to speak up).
> This API would just happen to be one possible library that can be built on
> top of 1), but one library which semantics has proven to be useful and
> popular among devs.
>
> Would that satisfy everyone?

Maybe, although I fear that the distinction isn't all that clear-cut,
and library writers will still feel encouraged to build
non-compositional abstractions on top of the high-level API. After
all, one programmer's high-level API is the next programmer's
low-level API.

I have a strong feeling that the request for implicit flattening stems
from the desire to somehow have your cake and eat it too regarding the
choice between future semantics (1) and (2) above. Even if you want
flattening, isn't an explicit flattening method good enough?

/Andreas
David Bruant
2013-04-26 12:29:10 UTC
Permalink
Le 26/04/2013 13:24, Andreas Rossberg a ?crit :
> On 26 April 2013 12:19, David Bruant <bruant.d at gmail.com> wrote:
>>> In particular, irregularity and exceptions become a pain
>>> when you start building abstractions, or plug together abstractions.
>>> In other words, regularity is a prerequisite for what some people
>>> (including me) like to call "compositionality".
>>>
>>> Flattening for futures/promises is irregular because it means that
>>> certain behaviour reliably exists in all cases _except_ when the input
>>> is itself a future/promise. This may not seem like a big deal if you
>>> use them directly. But now imagine somebody built a bigger generic
>>> abstraction that uses futures/promises only internally. You find that
>>> abstraction useful, and for whatever reason, need to funnel in a value
>>> that happens to be a future.
>> This last sentence and especially the ambiguous "for whatever reason" is the
>> heart of the debate I believe. The idea expressed by Kevin Smith of promises
>> as featureless values is that there should be no reason for you to funnel a
>> value that happens to be a promise unless it's expected to be a promise and
>> not a non-promise value.
>>
>> I have read somewhere (I can't remember where, hopefully MarkM will confirm
>> or say if I imagined it) that in E, if a variable contains a promise and
>> this promise is resolved, then the variable unwraps its value and
>> referencing to the variable suddenly means referencing to the value.
>> Promises are so deeply embedded in the language (syntax included) that this
>> unwrapping is seamless.
>> If I didn't imagine this, it'd be interesting if MarkM could expand on that
>> as it seems to imply that promises shouldn't be values but lower-level than
>> that.
>>
>> Even if he confirms, it probably won't be possible to have something that
>> embedded in JavaScript, but I think it's an interesting perspective.
> I'm not sure if your description of E is accurate -- I'd find that
> surprising. It _is_ a perfectly sensible design to have transparent
> futures that you can just use in place of the value they eventually
> get resolved to (let's call that value their 'content'). In fact, that
> is how futures/promises where originally proposed (back in the 70s)
> and implemented (e.g. in MultiLisp in 85, Oz in 95, and others later).
> However, there are really only two consistent points in the design
> space:
>
> 1. Either, futures are objects in their own right, with methods and
> everything. Then they should be fully compositional, and never be
> equated with their content, even after resolved (because that would
> change the meaning of a reference at locally unpredictable points in
> time).
>
> 2. Or, futures are entirely transparent, i.e. can be used everywhere
> as a placeholder for their content. In particular, that means that
> 'f.then' always denotes the 'then' method of the content, not of the
> future itself (even when the content value is not yet available).
>
> Unfortunately, option (2) requires that accessing a future that is not
> yet resolved has to (implicitly) block until it is (which is what
> happens in MultiLisp and friends).
I'm adventuring myself in places where I don't have experience, but I
dont think blocking is what *has to* happen. Programming languages I
know all have a local-by-default semantics, that is all values being
played with are expected to be local by default (which sort of makes
sense for in single-machine environments).
A programming language could take the opposite direction and consider
all values as remote-by-default. If values are remote, with event-loop
semantics, you never really block. Not more than is required to wait
actual remote values (which is incompressible anyway). If you have local
values abstracted as remote, the time you wait for the value is actually
just shorter than when you're waiting for a value at the other side of
the planet.
Promise pipelining [1] allows to interact with values (or give the
impression to) although they're not here yet.

> That only makes sense with threads,
> so I don't see how it can be reconciled with JavaScript.
I agree, it's certainly way too late to retrofit "remote by default
values" in JS. But the thought experiment is interesting. Maybe to be
tested in a compile-to-JS language.

>> One thing I have been convinced by your message is that it seems necessary
>> to provide the lowest-level non-flattening primitives so that people build
>> abstractions can that consider promises as values and will want to know
>> about nested promise types. It should be possible, but building such
>> abstractions doesn't seem like the 80% use case.
>> Based on my experience, I remain pretty strong on the fact that flattening
>> is a sensible default and really has practical advantages to large-scale
>> development.
>>
>> So, what about the following:
>> 1) Providing lowest-level non-flattening primitives so that library authors
>> have fine-grained control and can build their own abstraction where they can
>> consider promises as values (what is missing in the current API?)
>> 2) The API to be used by the average devs (then/catch/done/every/any/etc.)
>> has flattening semantics as this is what people with the most experience
>> have been advocating for (people with a different experience are still free
>> to speak up).
>> This API would just happen to be one possible library that can be built on
>> top of 1), but one library which semantics has proven to be useful and
>> popular among devs.
>>
>> Would that satisfy everyone?
> Maybe, although I fear that the distinction isn't all that clear-cut,
> and library writers will still feel encouraged to build
> non-compositional abstractions on top of the high-level API.
True. I don't see that as an issue. Or more accurately, it doesn't make
the non-compositional issue worse than it is today. However, providing
both APIs, expert enough users will know what they should build on top
of for the sake of good composability.
Non-expert users will keep making non-composable libraries; I think even
only providing the low-level API couldn't save them from themselves :-)

> I have a strong feeling that the request for implicit flattening stems
> from the desire to somehow have your cake and eat it too regarding the
> choice between future semantics (1) and (2) above.
I don't know for others and about cakes too much, but I've had a ~8
months experience working with promises on a Node.js application and
I've experienced the benefits of not worrying about nested promises and
really enjoyed it.
As a promise consumer (as opposed to a promise library author), I didn't
miss promise<promise<T>> a single second. I wish the same experience for
whoever is using web platform promises.
As many called out, where are the libraries with no-flattening
semantics? Where is the experience with these libraries? What were the
benefits and downsides of using these libraries?
I believe libraries converged to flattening semantics out of experience.
I believe (though can't prove it formally) that non-flattening may have
existed but proved inefficient when used at scale. More on that below.

> Even if you want
> flattening, isn't an explicit flattening method good enough?
I can't answer definitively by lack of experience with non-flattening
semantics + explicit flattening method, but I'm afraid it will result in
boilerplate:
Without flattening semantics, if a function changes of return value from
Future<T> to Future<Future<T>>, then 2 things can happen:
1) The function does change of signature and all client of this function
must call the flattening method
2) The function doesn't change it signature and calls the flattening
method before returning

Some will forget to call it before return, we can imagine that in JS,
some functions will return both Future<T> and Future<Future<T>> on
different path (that's JavaScript, on the web, that'll happen, on
purpose or by mistake) and suddenly, you end up calling the flattening
function a lot (boilerplate). It wouldn't be surprising if blog posts or
doc suggest to call it "just to be sure" at the beginning of every
function taking a promise as argument (boilerplate as a good practice :-s).
In the end, instead of calling it all the time "just to be sure" or
being afraid that your code break because of a change in nesting depth,
you just wish that it was all taken care of by the promise
infrastructure (what I called "hiding under the carpet" in an earlier
post)...

I can't speak for them, but I believe what I've described (starting at a
function changing of signature or a function being able to return
different promise nesting level) is the reason popular promise libraries
converged to a flattening semantics.

David

[1] http://erights.org/elib/distrib/pipeline.html
Andreas Rossberg
2013-04-26 13:38:06 UTC
Permalink
On 26 April 2013 14:29, David Bruant <bruant.d at gmail.com> wrote:
> Le 26/04/2013 13:24, Andreas Rossberg a ?crit :
>> I'm not sure if your description of E is accurate -- I'd find that
>> surprising. It _is_ a perfectly sensible design to have transparent
>> futures that you can just use in place of the value they eventually
>> get resolved to (let's call that value their 'content'). In fact, that
>> is how futures/promises where originally proposed (back in the 70s)
>> and implemented (e.g. in MultiLisp in 85, Oz in 95, and others later).
>> However, there are really only two consistent points in the design
>> space:
>>
>> 1. Either, futures are objects in their own right, with methods and
>> everything. Then they should be fully compositional, and never be
>> equated with their content, even after resolved (because that would
>> change the meaning of a reference at locally unpredictable points in
>> time).
>>
>> 2. Or, futures are entirely transparent, i.e. can be used everywhere
>> as a placeholder for their content. In particular, that means that
>> 'f.then' always denotes the 'then' method of the content, not of the
>> future itself (even when the content value is not yet available).
>>
>> Unfortunately, option (2) requires that accessing a future that is not
>> yet resolved has to (implicitly) block until it is (which is what
>> happens in MultiLisp and friends).
>
> I'm adventuring myself in places where I don't have experience, but I dont
> think blocking is what *has to* happen. Programming languages I know all
> have a local-by-default semantics, that is all values being played with are
> expected to be local by default (which sort of makes sense for in
> single-machine environments).
> A programming language could take the opposite direction and consider all
> values as remote-by-default. If values are remote, with event-loop
> semantics, you never really block.

Note that futures/promises, by themselves, do not have anything to do
with distribution. But yes, you could implicitly wrap _every_
operation into a separate computation returning a future. That would
be pretty close to what lazy languages do (a lazy thunk is basically a
form of future). Unfortunately, it is also known to interact horribly
with side effects, by making their order highly unpredictable (despite
the semantics still being deterministic).

/Andreas
Kevin Smith
2013-04-26 12:54:26 UTC
Permalink
What exactly is the controversy here?

I think we all agree with the semantics of "then" as specified in
Promises/A+. (If not, then we have a really big problem!)

If so, then the only real controversy is whether or not the API allows one
to create a promise whose eventual value is itself a promise. Q does not:
it provides only "resolve" and "reject". DOM Futures do by way of
"Future.accept". As far as I know, there's nothing about Q's
implementation that would make such a function impossible, it just does not
provide one.

Do I have that right so far?

{ Kevin }
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130426/cc0bcaf1/attachment-0001.html>
Alex Russell
2013-04-26 13:28:04 UTC
Permalink
Yes, you do.
On Apr 26, 2013 2:54 PM, "Kevin Smith" <zenparsing at gmail.com> wrote:

> What exactly is the controversy here?
>
> I think we all agree with the semantics of "then" as specified in
> Promises/A+. (If not, then we have a really big problem!)
>
> If so, then the only real controversy is whether or not the API allows one
> to create a promise whose eventual value is itself a promise. Q does not:
> it provides only "resolve" and "reject". DOM Futures do by way of
> "Future.accept". As far as I know, there's nothing about Q's
> implementation that would make such a function impossible, it just does not
> provide one.
>
> Do I have that right so far?
>
> { Kevin }
>
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130426/ec865943/attachment.html>
Kevin Smith
2013-04-26 13:31:46 UTC
Permalink
On Fri, Apr 26, 2013 at 9:28 AM, Alex Russell <slightlyoff at google.com>wrote:

> Yes, you do.
>
Mark or Domenic, is the point about Q true as well? (That it could, in
principle, provide something like Future.accept, but it chooses not to.)

Just wanted to check before I say somethin' foolish : )

{ Kevin }
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130426/53dca86b/attachment.html>
David Bruant
2013-04-26 13:36:57 UTC
Permalink
Le 26/04/2013 14:54, Kevin Smith a ?crit :
> What exactly is the controversy here?
>
> I think we all agree with the semantics of "then" as specified in
> Promises/A+. (If not, then we have a really big problem!)
>
> If so, then the only real controversy is whether or not the API allows
> one to create a promise whose eventual value is itself a promise. Q
> does not: it provides only "resolve" and "reject". DOM Futures do by
> way of "Future.accept". As far as I know, there's nothing about Q's
> implementation that would make such a function impossible, it just
> does not provide one.
I believe at this point the question isn't so much "can I build a
promise for a promise?", but rather "what should be the default Future
semantics?"
Namely:

Future.accept(5)
.then(function(x){
return Future.accept(x);
})
.then(function(y){
// is y a Future?
})

I'm arguing in favor of y being guaranteed to be a non-Future value. It
is my understanding others would want y to be a Future.
That would be the controversy as I understand it.

David
Dean Landolt
2013-04-26 14:25:50 UTC
Permalink
On Fri, Apr 26, 2013 at 9:36 AM, David Bruant <bruant.d at gmail.com> wrote:

> Le 26/04/2013 14:54, Kevin Smith a ?crit :
>
>> What exactly is the controversy here?
>>
>> I think we all agree with the semantics of "then" as specified in
>> Promises/A+. (If not, then we have a really big problem!)
>>
>> If so, then the only real controversy is whether or not the API allows
>> one to create a promise whose eventual value is itself a promise. Q does
>> not: it provides only "resolve" and "reject". DOM Futures do by way of
>> "Future.accept". As far as I know, there's nothing about Q's
>> implementation that would make such a function impossible, it just does not
>> provide one.
>>
> I believe at this point the question isn't so much "can I build a promise
> for a promise?", but rather "what should be the default Future semantics?"
> Namely:
>
> Future.accept(5)
> .then(function(x){
> return Future.accept(x);
> })
> .then(function(y){
> // is y a Future?
> })
>
> I'm arguing in favor of y being guaranteed to be a non-Future value. It is
> my understanding others would want y to be a Future.
> That would be the controversy as I understand it.
>


Reframing this in terms of resolution semantics may be helpful. I think
others would want a way to resolve one promise without recursively
resolving them all. That doesn't mean that the default resolution semantics
can't recursively resolve. Recursive resolve can be built on top of a
single-step resolver, but even so, it would be useful to also provide (and
recommend) recursive resolve resolve since it has the resolution semantics
demanded by the vast majority of use cases.

The fundamental controversy, as Juan just noted, is how to precisely
identify a promise in order to do either of these two things. This problem
isn't quite so clean cut, but it's much more important to solve. I've been
trying to bring some attention to it over the last few days -- I hope it's
clear that a `then` method is not enough to identify a promise language
construct -- this will subtly break existing code (e.g. casperjs).

In passing people keep assuming a well-known symbol could solve this
problem, but haven't offered any ideas for compatibility with es5 code. I
can't see how we could shim enough of the module system to use it as a
registry here, but maybe we could hang a symbol of a built-in Promise class
that could be shimmed as a random string. Given a built-in Promise class I
believe an `instanceof` branding could be made to work in a shimmable
manner too, with __proto__ hacking getting you the rest of the way there if
it really comes to it (es5 cross-frame wackiness, for instance).
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130426/088d30e8/attachment.html>
Andreas Rossberg
2013-04-26 15:18:30 UTC
Permalink
On 26 April 2013 16:25, Dean Landolt <dean at deanlandolt.com> wrote:
> The fundamental controversy, as Juan just noted, is how to precisely
> identify a promise in order to do either of these two things. This problem
> isn't quite so clean cut, but it's much more important to solve. I've been
> trying to bring some attention to it over the last few days -- I hope it's
> clear that a `then` method is not enough to identify a promise language
> construct -- this will subtly break existing code (e.g. casperjs).

Let me note that this is not the fundamental controversy (not for me,
anyway). The fundamental controversy is whether there should be any
irregularity at all, as is unavoidably introduced by implicit
flattening. The problem you describe just makes the negative effect of
that irregularity worse.

/Andreas
Dean Landolt
2013-04-27 21:21:31 UTC
Permalink
On Fri, Apr 26, 2013 at 11:18 AM, Andreas Rossberg <rossberg at google.com>wrote:

> On 26 April 2013 16:25, Dean Landolt <dean at deanlandolt.com> wrote:
> > The fundamental controversy, as Juan just noted, is how to precisely
> > identify a promise in order to do either of these two things. This
> problem
> > isn't quite so clean cut, but it's much more important to solve. I've
> been
> > trying to bring some attention to it over the last few days -- I hope
> it's
> > clear that a `then` method is not enough to identify a promise language
> > construct -- this will subtly break existing code (e.g. casperjs).
>
> Let me note that this is not the fundamental controversy (not for me,
> anyway). The fundamental controversy is whether there should be any
> irregularity at all, as is unavoidably introduced by implicit
> flattening. The problem you describe just makes the negative effect of
> that irregularity worse.
>


It may be a little late (I'm just catching up on these promise megathreads)
but I was suggesting that the irregularity posed by flattening is only a
distraction. AFAICT you're concern with flattening is actually in regard to
resolution semantics, right? Otherwise it's unobservable whether you have a
Promise<value> or a Promise<Promise<value>>. I'm trying to argue that given
a reliable branding a one-step resolver is easy and sensible to define (no
flattening), and a recursive resolver is an obvious extension. Almost
everyone would use the latter, but I completely agree that the former is
necessary too. No irregularities, everybody wins.

I don't think I've been able to properly communicate the gravity of the
branding issue. I'll try code:


getAFuture()
.then(function(x) {
return require('casper').create();
});


Assuming you know that factory returns a Casper instance, you would
probably expect a Promise<Casper> value, right? But Casper instances have
`then` methods, so it'll quack like a promise to the Promises/A+
assimilation algorithm, so what gets returned will be a Promise<undefined>
value. You may luck out and notice some non-sensical behavior, but it's
very possible this code could *almost* work just fine -- right until it
doesn't. It's more than just a debug hazard -- this kind of code could
easily slip into production even with awesome test coverage.

Casper's just a convenient example -- this problem applies to all
"thenables" that aren't Promises/A+ promises. What's worse, the semantics
of the word `then` imply that many of these thenable objects will probably
*almost* work in the same way as the example above.


To my mind the question of flattening sorts itself out as soon as you
define a means to reliably identify a promise. But it could also be
sidestepped completely, which I believe is the option Dave Herman favored
at one point...

It's been a few years but I recall an exchange we had where he took the
position that there shouldn't even be a method to test whether a value is a
promise -- IIRC he was arguing that any `value | Promise<value>`
functionality was unnecessary, and even hazardous. I was never clear on
exactly how this could be made to work, especially in interoperable
libraries, but as a language construct I suppose it's feasible. I'm curious
to hear where Dave stands now -- he eventually built support for thenables
into task.js (which I think was what sparked this exchange) but that could
have been out of convenience. Of course, with this approach I can't imagine
how promises could possibly be made nestable with one-step resolution (what
people seem to be calling "monadic" now?).
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130427/99831167/attachment.html>
Tab Atkins Jr.
2013-04-27 22:49:05 UTC
Permalink
On Sat, Apr 27, 2013 at 2:21 PM, Dean Landolt <dean at deanlandolt.com> wrote:
> On Fri, Apr 26, 2013 at 11:18 AM, Andreas Rossberg <rossberg at google.com>
> wrote:
>> On 26 April 2013 16:25, Dean Landolt <dean at deanlandolt.com> wrote:
>> > The fundamental controversy, as Juan just noted, is how to precisely
>> > identify a promise in order to do either of these two things. This
>> > problem
>> > isn't quite so clean cut, but it's much more important to solve. I've
>> > been
>> > trying to bring some attention to it over the last few days -- I hope
>> > it's
>> > clear that a `then` method is not enough to identify a promise language
>> > construct -- this will subtly break existing code (e.g. casperjs).
>>
>> Let me note that this is not the fundamental controversy (not for me,
>> anyway). The fundamental controversy is whether there should be any
>> irregularity at all, as is unavoidably introduced by implicit
>> flattening. The problem you describe just makes the negative effect of
>> that irregularity worse.
>
> It may be a little late (I'm just catching up on these promise megathreads)
> but I was suggesting that the irregularity posed by flattening is only a
> distraction. AFAICT you're concern with flattening is actually in regard to
> resolution semantics, right? Otherwise it's unobservable whether you have a
> Promise<value> or a Promise<Promise<value>>. I'm trying to argue that given
> a reliable branding a one-step resolver is easy and sensible to define (no
> flattening), and a recursive resolver is an obvious extension. Almost
> everyone would use the latter, but I completely agree that the former is
> necessary too. No irregularities, everybody wins.

Given the history of this issue so far, and the persistent
miscommunications, I'd like to make sure that you understand what is
meant by *non*-recursive flattening, which is what we on the monad
side are arguing for.

In a world with non-recursive flattening, like I want, this code:

getAPromise()
.then(function(x) {
return 5;
}).then(function(y) {
alert(y);
});

and this code:

getAPromise()
.then(function(x) {
return Promise.accept(5);
}).then(function(y) {
alert(y);
});

will both alert "5", not "<object Promise>".

The only way to get it to alert "<object Promise>" is with code like this:

getAPromise()
.then(function(x) {
return Promise.accept(Promise.accept(5));
}).then(function(y) {
alert(y);
});

That is, you have to very explicitly double up on Promises. It
doesn't happen by accident.

It's only this last case, which should be rare unless you're doing it
on purpose, which we're all arguing about. The recursive-flatten
people want this case (and all higher-stacked cases, like "return
Promise.accept(Promise.accept(Promise.accept(5)));") to all alert "5".

This breaks some useful invariants, though. It means that Promises
are no longer monads, which prevents you from using monad libraries.
It also means that you have to hack around the behavior in other cases
- if you omit a callback from .then(), it's supposed to just "pass
through" for that case, so the next .then() call to provide the
callback gets the exact same value as it would if the callback were
moved up. This doesn't work with recursive-flattening, though - if
you make a nested Promise explicitly, and then call .then(cb) on it,
the callback will get a promise as an argument, but if you call
.then().then(cb), the promise will have been flattened out. You have
to insert a special hack into the resolution mechanics to make this
not happen.

Since nested promises are hard to create unless you're doing it on
purpose, can be useful, and produce more consistent semantics overall,
we should be using non-recursive flattening. The only place where you
want recursive flattening is when you're *assimilating an
outside-world promise* from a foreign library, which should require an
explicit action (so you don't end up "flattening" something like a
Casper.js value, and getting nonsense out as a result). We can just
follow Promises/A+'s flattening semantics, but invoke them from some
explicit function. Best of all worlds.

> It's been a few years but I recall an exchange we had where he took the
> position that there shouldn't even be a method to test whether a value is a
> promise -- IIRC he was arguing that any `value | Promise<value>`
> functionality was unnecessary, and even hazardous. I was never clear on
> exactly how this could be made to work, especially in interoperable
> libraries, but as a language construct I suppose it's feasible. I'm curious
> to hear where Dave stands now -- he eventually built support for thenables
> into task.js (which I think was what sparked this exchange) but that could
> have been out of convenience. Of course, with this approach I can't imagine
> how promises could possibly be made nestable with one-step resolution (what
> people seem to be calling "monadic" now?).

This is the position that E takes, but it has special language support
for promises, and does the unwrapping by itself, automatically.
That's fine, and it produces a non-monadic form of promises. JS
likely can't do that, though - it's stuck with promises as "real"
things, distinct from the values they wrap, so it should make the best
of it and make them monadic.

(If you don't understand the term "monadic", look at my earlier post
in this thread, where I gave a short primer. If you're still having
trouble, let me know privately, and I'd be glad to explain it in more
detail.)

~TJ
Mark Miller
2013-04-28 00:50:19 UTC
Permalink
On Sat, Apr 27, 2013 at 3:49 PM, Tab Atkins Jr. <jackalmage at gmail.com>wrote:

> On Sat, Apr 27, 2013 at 2:21 PM, Dean Landolt <dean at deanlandolt.com>
> wrote:
> > On Fri, Apr 26, 2013 at 11:18 AM, Andreas Rossberg <rossberg at google.com>
> > wrote:
> >> On 26 April 2013 16:25, Dean Landolt <dean at deanlandolt.com> wrote:
> >> > The fundamental controversy, as Juan just noted, is how to precisely
> >> > identify a promise in order to do either of these two things. This
> >> > problem
> >> > isn't quite so clean cut, but it's much more important to solve. I've
> >> > been
> >> > trying to bring some attention to it over the last few days -- I hope
> >> > it's
> >> > clear that a `then` method is not enough to identify a promise
> language
> >> > construct -- this will subtly break existing code (e.g. casperjs).
> >>
> >> Let me note that this is not the fundamental controversy (not for me,
> >> anyway). The fundamental controversy is whether there should be any
> >> irregularity at all, as is unavoidably introduced by implicit
> >> flattening. The problem you describe just makes the negative effect of
> >> that irregularity worse.
> >
> > It may be a little late (I'm just catching up on these promise
> megathreads)
> > but I was suggesting that the irregularity posed by flattening is only a
> > distraction. AFAICT you're concern with flattening is actually in regard
> to
> > resolution semantics, right? Otherwise it's unobservable whether you
> have a
> > Promise<value> or a Promise<Promise<value>>. I'm trying to argue that
> given
> > a reliable branding a one-step resolver is easy and sensible to define
> (no
> > flattening), and a recursive resolver is an obvious extension. Almost
> > everyone would use the latter, but I completely agree that the former is
> > necessary too. No irregularities, everybody wins.
>
> Given the history of this issue so far, and the persistent
> miscommunications, I'd like to make sure that you understand what is
> meant by *non*-recursive flattening, which is what we on the monad
> side are arguing for.
>
> In a world with non-recursive flattening, like I want, this code:
>
> getAPromise()
> .then(function(x) {
> return 5;
> }).then(function(y) {
> alert(y);
> });
>
> and this code:
>
> getAPromise()
> .then(function(x) {
> return Promise.accept(5);
> }).then(function(y) {
> alert(y);
> });
>
> will both alert "5", not "<object Promise>".
>
> The only way to get it to alert "<object Promise>" is with code like this:
>
> getAPromise()
> .then(function(x) {
> return Promise.accept(Promise.accept(5));
> }).then(function(y) {
> alert(y);
> });
>
> That is, you have to very explicitly double up on Promises. It
> doesn't happen by accident.
>
> It's only this last case, which should be rare unless you're doing it
> on purpose, which we're all arguing about. The recursive-flatten
> people want this case (and all higher-stacked cases, like "return
> Promise.accept(Promise.accept(Promise.accept(5)));") to all alert "5".
>
> This breaks some useful invariants, though. It means that Promises
> are no longer monads, which prevents you from using monad libraries.
> It also means that you have to hack around the behavior in other cases
> - if you omit a callback from .then(), it's supposed to just "pass
> through" for that case, so the next .then() call to provide the
> callback gets the exact same value as it would if the callback were
> moved up. This doesn't work with recursive-flattening, though - if
> you make a nested Promise explicitly, and then call .then(cb) on it,
> the callback will get a promise as an argument, but if you call
> .then().then(cb), the promise will have been flattened out. You have
> to insert a special hack into the resolution mechanics to make this
> not happen.
>
> Since nested promises are hard to create unless you're doing it on
> purpose, can be useful, and produce more consistent semantics overall,
> we should be using non-recursive flattening. The only place where you
> want recursive flattening is when you're *assimilating an
> outside-world promise* from a foreign library, which should require an
> explicit action (so you don't end up "flattening" something like a
> Casper.js value, and getting nonsense out as a result). We can just
> follow Promises/A+'s flattening semantics, but invoke them from some
> explicit function. Best of all worlds.
>
> > It's been a few years but I recall an exchange we had where he took the
> > position that there shouldn't even be a method to test whether a value
> is a
> > promise -- IIRC he was arguing that any `value | Promise<value>`
> > functionality was unnecessary, and even hazardous. I was never clear on
> > exactly how this could be made to work, especially in interoperable
> > libraries, but as a language construct I suppose it's feasible. I'm
> curious
> > to hear where Dave stands now -- he eventually built support for
> thenables
> > into task.js (which I think was what sparked this exchange) but that
> could
> > have been out of convenience. Of course, with this approach I can't
> imagine
> > how promises could possibly be made nestable with one-step resolution
> (what
> > people seem to be calling "monadic" now?).
>
> This is the position that E takes,


I'm not sure which piece "this" refers to, but in E it is possible to test
whether a value is an unresolved promise. Testing this is discouraged, but
is sometimes necessary for various meta-programming tasks, like
serialization. It is not possible to test whether a value is a fulfilled
promise, since a fulfilled promise really is indistinguishable from its
fulfillment value.



> but it has special language support
> for promises, and does the unwrapping by itself, automatically.
> That's fine, and it produces a non-monadic form of promises. JS
> likely can't do that, though - it's stuck with promises as "real"
> things, distinct from the values they wrap, so it should make the best
> of it and make them monadic.
>
> (If you don't understand the term "monadic", look at my earlier post
> in this thread, where I gave a short primer. If you're still having
> trouble, let me know privately, and I'd be glad to explain it in more
> detail.)
>
> ~TJ
>



--
Text by me above is hereby placed in the public domain

Cheers,
--MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130427/210c089e/attachment.html>
Tab Atkins Jr.
2013-04-26 20:39:30 UTC
Permalink
On Fri, Apr 26, 2013 at 6:36 AM, David Bruant <bruant.d at gmail.com> wrote:
> Le 26/04/2013 14:54, Kevin Smith a ?crit :
>>
>> What exactly is the controversy here?
>>
>> I think we all agree with the semantics of "then" as specified in
>> Promises/A+. (If not, then we have a really big problem!)
>>
>> If so, then the only real controversy is whether or not the API allows one
>> to create a promise whose eventual value is itself a promise. Q does not:
>> it provides only "resolve" and "reject". DOM Futures do by way of
>> "Future.accept". As far as I know, there's nothing about Q's implementation
>> that would make such a function impossible, it just does not provide one.
>
> I believe at this point the question isn't so much "can I build a promise
> for a promise?", but rather "what should be the default Future semantics?"
> Namely:
>
> Future.accept(5)
> .then(function(x){
> return Future.accept(x);
> })
> .then(function(y){
> // is y a Future?
> })
>
> I'm arguing in favor of y being guaranteed to be a non-Future value. It is
> my understanding others would want y to be a Future.
> That would be the controversy as I understand it.

No. Future callbacks can return Futures, which then chain (the return
value of then adopts the state of the callback's return value). This
is the big "monad" benefit that we keep talking about.

The only way to get y to be a future is to have the first function do
"return Future.accept(Future.accept(x));". In other words, you have
to be pretty explicit about this.

The fact that Future callbacks can return either plain values *or*
Futures, and they act consistently with both (sending a plain value to
the next callback in the chain) is extremely powerful and usable, and
means that you basically *never* have to explicitly wrap a callback,
or even worry about the return value of functions. As long as your
callback is simply returning the function's return value, it can be a
Future *or* a plain value, and it'll just *work* with you none the
wiser.

If other people on the "recursive flattening" side have the same
misunderstanding here that you're showing, no *wonder* we're all
arguing with each other so fiercely! You're completely correct that
having Y be a Future would be *terrible*, and that's precisely why
Futures work the way they do already.

(For the uninitiated, the chaining behavior is part of the monad
contract. If y was a Future, it would mean that Futures were only
functors (a weaker abstraction than monads). Luckily, they're not.)

(By the way, apologies if any of this sounds insulting. It's
unintentional! I can't figure out a way to phrase "You're wrong, and
everyone else might be wrong in the same way" in an unambiguously
polite manner. ^_^)

~TJ
Tab Atkins Jr.
2013-04-26 20:41:54 UTC
Permalink
On Fri, Apr 26, 2013 at 1:39 PM, Tab Atkins Jr. <jackalmage at gmail.com> wrote:
[snip]
> No. Future callbacks can return Futures, which then chain (the return
> value of then adopts the state of the callback's return value). This
> is the big "monad" benefit that we keep talking about.
[snip]

Shorter me: this is why I keep asking people who want flattening to
actually provide an example of where flattening is useful, that isn't
(a) assimilation, (b) a result of weird language semantics from some
non-JS language, or (c) an authoring error.

So far, I haven't gotten one! Every concrete example I've received so
far just shows off assimilation, or a misunderstanding of what JS
promises/futures do. :/

~TJ
Domenic Denicola
2013-04-26 20:45:28 UTC
Permalink
From: Tab Atkins Jr. [jackalmage at gmail.com]

> Shorter me: this is why I keep asking people who want flattening to actually provide an example of where flattening is useful, that isn't (a) assimilation, (b) a result of weird language semantics from some non-JS language, or (c) an authoring error.

Since (multi-level) flattening only occurs for assimilation (per Promises/A+ 1.1), it appears we have been talking past each other. All examples of multi-level flattening will necessarily be examples of assimilation.
Tab Atkins Jr.
2013-04-26 20:51:14 UTC
Permalink
On Fri, Apr 26, 2013 at 1:45 PM, Domenic Denicola
<domenic at domenicdenicola.com> wrote:
> From: Tab Atkins Jr. [jackalmage at gmail.com]
>> Shorter me: this is why I keep asking people who want flattening to actually provide an example of where flattening is useful, that isn't (a) assimilation, (b) a result of weird language semantics from some non-JS language, or (c) an authoring error.
>
> Since (multi-level) flattening only occurs for assimilation (per Promises/A+ 1.1), it appears we have been talking past each other. All examples of multi-level flattening will necessarily be examples of assimilation.

In that case, HUZZAH! We've solved the problem! Assimilation is not
a troublesome issue; if that works best when done recursively, go for
it. Presumably, the reason it works best when done recursively is
that if the value passed between multiple non-assimilating promise
concepts, it might have gotten multi-wrapped, once for each promise
concept (or maybe more than once for each, if the layers are separated
by other types of promises). So, it's more useful to just assume that
multi-wrapped thenables were done accidentally, and should be fully
flattened.

If that's all we need to do, and we can have an explicit assimilation
function in the standard that takes arbitrary thenables (per the
Promises/A+ spec), then we can leave .then() alone and give it the
correct monadic semantics. It's very difficult to accidentally get
double-wrapped within a single library with proper semantics, and
doing so indicates a fundamental logic error and so *should* give
funky results, rather than having the error accidentally papered over.

~TJ
Mark Miller
2013-04-27 14:38:02 UTC
Permalink
On Fri, Apr 26, 2013 at 1:51 PM, Tab Atkins Jr. <jackalmage at gmail.com>wrote:

> On Fri, Apr 26, 2013 at 1:45 PM, Domenic Denicola
> <domenic at domenicdenicola.com> wrote:
> > From: Tab Atkins Jr. [jackalmage at gmail.com]
> >> Shorter me: this is why I keep asking people who want flattening to
> actually provide an example of where flattening is useful, that isn't (a)
> assimilation, (b) a result of weird language semantics from some non-JS
> language, or (c) an authoring error.
> >
> > Since (multi-level) flattening only occurs for assimilation (per
> Promises/A+ 1.1), it appears we have been talking past each other. All
> examples of multi-level flattening will necessarily be examples of
> assimilation.
>
> In that case, HUZZAH! We've solved the problem!


We may have.

When I argue for default flattening (#0 in my "What Are We Arguing About")
and you argue against, you claim default flattening is not monadic, which I
agree with. But then you go on to explain as "monadic" a semantics that
seems like default flattening to me. I am curious if you could define what
you mean by "monadic". I don't much care if we call a promise system
"monadic" or not, but let's not let a disagreement on this term obscure a
possible agreement on what promises should do.

As I made clear in my "What Are We Arguing About" email, I want to separate
the argument about default flattening (#0) from the argument about whether
promises-for-promises are possible (#1), and from arguments about thenables
and assimilation (#2,#3,#4).

AFAICT, leaving aside operations that would explicitly create
promises-for-promises, i.e., "fulfill" (aka "accept"), I don't see how it
is possible to create promises-for-promises with the remaining operations
you and I seem to agree on: Q(x) (aka "Future.resolve(x)"), "then",
"resolve", "reject". If promise-for-promises cannot be made in the first
place, then no recursive unwrapping is required to take them apart.
Specifically, assimilation aside and API details aside, what about Q do you
disagree with, if anything?

If we can narrow our remaining disagreements down to #1..#7, that would be
great progress. Yes, #1..#7 will still be a lot of work, but great progress
nonetheless. And I will leave to you and other to fight about what is and
isn't called "monadic" ;).




> Assimilation is not
> a troublesome issue; if that works best when done recursively, go for
> it. Presumably, the reason it works best when done recursively is
> that if the value passed between multiple non-assimilating promise
> concepts, it might have gotten multi-wrapped, once for each promise
> concept (or maybe more than once for each, if the layers are separated
> by other types of promises). So, it's more useful to just assume that
> multi-wrapped thenables were done accidentally, and should be fully
> flattened.
>
> If that's all we need to do, and we can have an explicit assimilation
> function in the standard that takes arbitrary thenables (per the
> Promises/A+ spec), then we can leave .then() alone and give it the
> correct monadic semantics. It's very difficult to accidentally get
> double-wrapped within a single library with proper semantics, and
> doing so indicates a fundamental logic error and so *should* give
> funky results, rather than having the error accidentally papered over.
>
> ~TJ
>



--
Text by me above is hereby placed in the public domain

Cheers,
--MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130427/47d3ccfa/attachment.html>
Tab Atkins Jr.
2013-04-27 16:28:00 UTC
Permalink
On Sat, Apr 27, 2013 at 7:38 AM, Mark Miller <erights at gmail.com> wrote:
> On Fri, Apr 26, 2013 at 1:51 PM, Tab Atkins Jr. <jackalmage at gmail.com>
> wrote:
>> On Fri, Apr 26, 2013 at 1:45 PM, Domenic Denicola
>> <domenic at domenicdenicola.com> wrote:
>> > From: Tab Atkins Jr. [jackalmage at gmail.com]
>> >> Shorter me: this is why I keep asking people who want flattening to
>> >> actually provide an example of where flattening is useful, that isn't (a)
>> >> assimilation, (b) a result of weird language semantics from some non-JS
>> >> language, or (c) an authoring error.
>> >
>> > Since (multi-level) flattening only occurs for assimilation (per
>> > Promises/A+ 1.1), it appears we have been talking past each other. All
>> > examples of multi-level flattening will necessarily be examples of
>> > assimilation.
>>
>> In that case, HUZZAH! We've solved the problem!
>
>
> We may have.
>
> When I argue for default flattening (#0 in my "What Are We Arguing About")
> and you argue against, you claim default flattening is not monadic, which I
> agree with. But then you go on to explain as "monadic" a semantics that
> seems like default flattening to me. I am curious if you could define what
> you mean by "monadic". I don't much care if we call a promise system
> "monadic" or not, but let's not let a disagreement on this term obscure a
> possible agreement on what promises should do.

Hmm, that's strange. I just mean "following the monad laws". In
other words, Promises are monads, with .then as the monadic operation,
taking a function of type "a -> Promise<b>", and resulting in a new
Promise. (For convenience, .then() is also the functor operation,
allowing its function to be of type "a -> b", as long as "b" isn't a
Promise. But that doesn't harm the monad-ness, as long as you follow
the laws.) Future.accept (or its equivalent in a final Promise spec)
is the monadic lift operation.

We don't expose it explicitly (though we could), but the monadic
"join" operation takes a Promise<Promise<a>>, and returns a new
promise that waits for both the inner and outer to accept, then
accepts with the inner's state. If either rejects, the output promise
rejects with the same reason.

Did you think I meant something else? If so, what? And, do you think
I've made a mistake in describing the monadic properties of promises?
If so, what?

> As I made clear in my "What Are We Arguing About" email, I want to separate
> the argument about default flattening (#0) from the argument about whether
> promises-for-promises are possible (#1), and from arguments about thenables
> and assimilation (#2,#3,#4).
>
> AFAICT, leaving aside operations that would explicitly create
> promises-for-promises, i.e., "fulfill" (aka "accept"), I don't see how it is
> possible to create promises-for-promises with the remaining operations you
> and I seem to agree on: Q(x) (aka "Future.resolve(x)"), "then", "resolve",
> "reject". If promise-for-promises cannot be made in the first place, then no
> recursive unwrapping is required to take them apart. Specifically,
> assimilation aside and API details aside, what about Q do you disagree with,
> if anything?

Correct - if you leave out the only promise constructor that can
actually make promises-for-promises, you can't make
promises-for-promises. ^_^ Thus, recursive unwrapping, outside of
the assimilation use-case, isn't useful, as it's very hard to nest
promises unless you're doing it on purpose. (And if you are doing it
on purpose, it's potentially useful to have it act monadic.)

> If we can narrow our remaining disagreements down to #1..#7, that would be
> great progress. Yes, #1..#7 will still be a lot of work, but great progress
> nonetheless. And I will leave to you and other to fight about what is and
> isn't called "monadic" ;).

No fights necessary, luckily - the monad laws are completely trivial,
and it's easy to prove something does or doesn't obey them.

~TJ
Mark Miller
2013-04-27 16:48:09 UTC
Permalink
Cool. I think we (at least you and I) have agreement on default flattening
(#0).

FWIW, the reason I'm surprised that you're calling this monadic is the need
for the dynamic test on 'as long as "b" isn't a Promise'. In other words,
the signature of .then (including the receiver and excluding the errback)
is overloaded as

promise<a> -> (a -> promise<b>) -> promise<b>
or
promise<a> -> (a -> b) -> promise<b> // when b is not itself a promise
type

My impression is that each of these overloads by itself is a different
monad operation and that testing the condition in the comment
violates parametricity. If these do correspond to two different monad
operations, then different laws cover each. A similar analysis applies to
Q(x) (aka Future.resolve(x)).

In any case, no matter. We agree (assimilation aside) that this is how
.then and Q should behave. Wonderful!




On Sat, Apr 27, 2013 at 9:28 AM, Tab Atkins Jr. <jackalmage at gmail.com>wrote:

> On Sat, Apr 27, 2013 at 7:38 AM, Mark Miller <erights at gmail.com> wrote:
> > On Fri, Apr 26, 2013 at 1:51 PM, Tab Atkins Jr. <jackalmage at gmail.com>
> > wrote:
> >> On Fri, Apr 26, 2013 at 1:45 PM, Domenic Denicola
> >> <domenic at domenicdenicola.com> wrote:
> >> > From: Tab Atkins Jr. [jackalmage at gmail.com]
> >> >> Shorter me: this is why I keep asking people who want flattening to
> >> >> actually provide an example of where flattening is useful, that
> isn't (a)
> >> >> assimilation, (b) a result of weird language semantics from some
> non-JS
> >> >> language, or (c) an authoring error.
> >> >
> >> > Since (multi-level) flattening only occurs for assimilation (per
> >> > Promises/A+ 1.1), it appears we have been talking past each other. All
> >> > examples of multi-level flattening will necessarily be examples of
> >> > assimilation.
> >>
> >> In that case, HUZZAH! We've solved the problem!
> >
> >
> > We may have.
> >
> > When I argue for default flattening (#0 in my "What Are We Arguing
> About")
> > and you argue against, you claim default flattening is not monadic,
> which I
> > agree with. But then you go on to explain as "monadic" a semantics that
> > seems like default flattening to me. I am curious if you could define
> what
> > you mean by "monadic". I don't much care if we call a promise system
> > "monadic" or not, but let's not let a disagreement on this term obscure a
> > possible agreement on what promises should do.
>
> Hmm, that's strange. I just mean "following the monad laws". In
> other words, Promises are monads, with .then as the monadic operation,
> taking a function of type "a -> Promise<b>", and resulting in a new
> Promise. (For convenience, .then() is also the functor operation,
> allowing its function to be of type "a -> b", as long as "b" isn't a
> Promise. But that doesn't harm the monad-ness, as long as you follow
> the laws.) Future.accept (or its equivalent in a final Promise spec)
> is the monadic lift operation.
>
> We don't expose it explicitly (though we could), but the monadic
> "join" operation takes a Promise<Promise<a>>, and returns a new
> promise that waits for both the inner and outer to accept, then
> accepts with the inner's state. If either rejects, the output promise
> rejects with the same reason.
>
> Did you think I meant something else? If so, what? And, do you think
> I've made a mistake in describing the monadic properties of promises?
> If so, what?
>
> > As I made clear in my "What Are We Arguing About" email, I want to
> separate
> > the argument about default flattening (#0) from the argument about
> whether
> > promises-for-promises are possible (#1), and from arguments about
> thenables
> > and assimilation (#2,#3,#4).
> >
> > AFAICT, leaving aside operations that would explicitly create
> > promises-for-promises, i.e., "fulfill" (aka "accept"), I don't see how
> it is
> > possible to create promises-for-promises with the remaining operations
> you
> > and I seem to agree on: Q(x) (aka "Future.resolve(x)"), "then",
> "resolve",
> > "reject". If promise-for-promises cannot be made in the first place,
> then no
> > recursive unwrapping is required to take them apart. Specifically,
> > assimilation aside and API details aside, what about Q do you disagree
> with,
> > if anything?
>
> Correct - if you leave out the only promise constructor that can
> actually make promises-for-promises, you can't make
> promises-for-promises. ^_^ Thus, recursive unwrapping, outside of
> the assimilation use-case, isn't useful, as it's very hard to nest
> promises unless you're doing it on purpose. (And if you are doing it
> on purpose, it's potentially useful to have it act monadic.)
>
> > If we can narrow our remaining disagreements down to #1..#7, that would
> be
> > great progress. Yes, #1..#7 will still be a lot of work, but great
> progress
> > nonetheless. And I will leave to you and other to fight about what is and
> > isn't called "monadic" ;).
>
> No fights necessary, luckily - the monad laws are completely trivial,
> and it's easy to prove something does or doesn't obey them.
>
> ~TJ
>



--
Text by me above is hereby placed in the public domain

Cheers,
--MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130427/6f6419ac/attachment-0001.html>
Tab Atkins Jr.
2013-04-27 16:54:10 UTC
Permalink
On Sat, Apr 27, 2013 at 9:48 AM, Mark Miller <erights at gmail.com> wrote:
> Cool. I think we (at least you and I) have agreement on default flattening
> (#0).

Yay for terminology confusion masquerading as disagreement!

> FWIW, the reason I'm surprised that you're calling this monadic is the need
> for the dynamic test on 'as long as "b" isn't a Promise'. In other words,
> the signature of .then (including the receiver and excluding the errback) is
> overloaded as
>
> promise<a> -> (a -> promise<b>) -> promise<b>
> or
> promise<a> -> (a -> b) -> promise<b> // when b is not itself a promise
> type
>
> My impression is that each of these overloads by itself is a different monad
> operation and that testing the condition in the comment violates
> parametricity. If these do correspond to two different monad operations,
> then different laws cover each. A similar analysis applies to Q(x) (aka
> Future.resolve(x)).
>
> In any case, no matter. We agree (assimilation aside) that this is how .then
> and Q should behave. Wonderful!

The reason this is still monadic is because, as long as you follow the
monad type requirements properly, passing a callback with signature "a
-> Promise<b>", it works as a monad. No need for tests, no special
behavior, nothing.

It's just that we *also* mix in the functor behavior into .then(), so
that, if you fail to satisfy the monad type requirements, we can fall
back to treating it as a functor. That's the second overload. (It's
not monadic.)

(It might be nice to explicitly provide a .map() operation following
the functor laws, but it isn't very important to me.)

I'm fine with monadic operations that do additional things when you go
beyond the basic type contract, just like I'm fine with calling
Array.of() the monadic lift operator for Arrays - if called with one
value, as the monad contract requires, it lifts the value into an
array. It can just *also* be called with 0 or 2+ arguments, and
that's fine.

~TJ
Tab Atkins Jr.
2013-04-26 21:23:53 UTC
Permalink
On Fri, Apr 26, 2013 at 1:39 PM, Tab Atkins Jr. <jackalmage at gmail.com> wrote:
> On Fri, Apr 26, 2013 at 6:36 AM, David Bruant <bruant.d at gmail.com> wrote:
>> Le 26/04/2013 14:54, Kevin Smith a ?crit :
>>>
>>> What exactly is the controversy here?
>>>
>>> I think we all agree with the semantics of "then" as specified in
>>> Promises/A+. (If not, then we have a really big problem!)
>>>
>>> If so, then the only real controversy is whether or not the API allows one
>>> to create a promise whose eventual value is itself a promise. Q does not:
>>> it provides only "resolve" and "reject". DOM Futures do by way of
>>> "Future.accept". As far as I know, there's nothing about Q's implementation
>>> that would make such a function impossible, it just does not provide one.
>>
>> I believe at this point the question isn't so much "can I build a promise
>> for a promise?", but rather "what should be the default Future semantics?"
>> Namely:
>>
>> Future.accept(5)
>> .then(function(x){
>> return Future.accept(x);
>> })
>> .then(function(y){
>> // is y a Future?
>> })
>>
>> I'm arguing in favor of y being guaranteed to be a non-Future value. It is
>> my understanding others would want y to be a Future.
>> That would be the controversy as I understand it.
>
> No. Future callbacks can return Futures, which then chain (the return
> value of then adopts the state of the callback's return value). This
> is the big "monad" benefit that we keep talking about.

To lay it out even more clearly for any bystanders, in the following code:

getAFuture()
.then(function(x) {
return doSomeWork(x);
})
.then(function(y) {
// is y a Future?
});

The answer to the question is "no", *regardless of whether doSomeWork
returns a plain value or a Future for a plain value*.

If doSomeWork() returns a plain value, the future returned by the
first .then() call accepts with that value. If doSomeWork() returns a
future that eventually accepts, the future returned by the first
.then() call waits until it accepts, and then also accepts with the
same value.

This all happens without recursive unwrapping. It's just a feature of
how this kind of thing works (it's a result of Futures matching the
"monad" abstraction).

The only way that y will become a Future is if doSomeWork() explicitly
and purposefully returns a future for a future for a value (in other
words, Future<Future<x>>). In this case, the future returned by the
first .then() call waits until the outer Future from the return value
finishes, then accepts with its value, and "this value" happens to be
a Future<x>.

This sort of thing does not happen accidentally. It's hard to make
nested futures unless you're doing it on purpose, or you have no idea
what you're doing. In the first case, we want to trust the author,
and in the second case, it's probably better for everyone involved if
the code fails in a fairly obvious way, rather than attempting to
paper over the problem. If you're competent and just doing the
natural thing, the API naturally gives you plain values in callback
arguments and singly-wrapped futures in return values.

This is why I've been arguing against recursive unwrapping in the general case.

~TJ
Ron Buckton
2013-04-27 16:21:43 UTC
Permalink
Here is a case where flattening helps:

function executeAndWaitForComplete(command) {
return getJSON(commandUrl + command)
.then(function (commandResult) {
if (commandResult.complete) {
return commandResult.model;
}
var statusUrl = commmandResult.statusUrl;
return pollForCommandComplete(statusUrl);
})
}

function pollForCommandComplete(statusUrl) {
return new Future(function(resolver) {
var poll = function (pollResult) {
if (pollResult.done) {
resolve.resolve(pollResult.model);
}
else {
setTimeout(function() {
getJSON(statusUrl).done(poll, resolver.reject);
}, 500);
}
}
getJSON(statusUrl).done(poll, resolver.reject);
});
}

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>>. In a non flattening world, the developer would have to change the function to:

function executeAndWaitForComplete(command) {
return new Future(function(resolver) {
getJSON(commandUrl + command)
.done(function (commandResult) {
if (commandResult.complete) {
resolver.resolve(commandResult.model);
}
var statusUrl = commmandResult.statusUrl;
pollForCommandComplete(statusUrl).done(resolver.resolve, resolver.reject);
}, resolver.reject)
});
}

With flattening, the first version is less code for the developer. With flattening its possible to run into odd errors without some kind of static analysis since JS is not type safe.

Ron

Sent from Windows Mail

From: Tab Atkins Jr.
Sent: ?Friday?, ?April? ?26?, ?2013 ?2?:?24? ?PM
To: David Bruant
Cc: Mark S. Miller, public-script-coord at w3.org, Mark Miller, Dean Tribble, es-discuss

On Fri, Apr 26, 2013 at 1:39 PM, Tab Atkins Jr. <jackalmage at gmail.com> wrote:
> On Fri, Apr 26, 2013 at 6:36 AM, David Bruant <bruant.d at gmail.com> wrote:
>> Le 26/04/2013 14:54, Kevin Smith a ?crit :
>>>
>>> What exactly is the controversy here?
>>>
>>> I think we all agree with the semantics of "then" as specified in
>>> Promises/A+. (If not, then we have a really big problem!)
>>>
>>> If so, then the only real controversy is whether or not the API allows one
>>> to create a promise whose eventual value is itself a promise. Q does not:
>>> it provides only "resolve" and "reject". DOM Futures do by way of
>>> "Future.accept". As far as I know, there's nothing about Q's implementation
>>> that would make such a function impossible, it just does not provide one.
>>
>> I believe at this point the question isn't so much "can I build a promise
>> for a promise?", but rather "what should be the default Future semantics?"
>> Namely:
>>
>> Future.accept(5)
>> .then(function(x){
>> return Future.accept(x);
>> })
>> .then(function(y){
>> // is y a Future?
>> })
>>
>> I'm arguing in favor of y being guaranteed to be a non-Future value. It is
>> my understanding others would want y to be a Future.
>> That would be the controversy as I understand it.
>
> No. Future callbacks can return Futures, which then chain (the return
> value of then adopts the state of the callback's return value). This
> is the big "monad" benefit that we keep talking about.

To lay it out even more clearly for any bystanders, in the following code:

getAFuture()
.then(function(x) {
return doSomeWork(x);
})
.then(function(y) {
// is y a Future?
});

The answer to the question is "no", *regardless of whether doSomeWork
returns a plain value or a Future for a plain value*.

If doSomeWork() returns a plain value, the future returned by the
first .then() call accepts with that value. If doSomeWork() returns a
future that eventually accepts, the future returned by the first
.then() call waits until it accepts, and then also accepts with the
same value.

This all happens without recursive unwrapping. It's just a feature of
how this kind of thing works (it's a result of Futures matching the
"monad" abstraction).

The only way that y will become a Future is if doSomeWork() explicitly
and purposefully returns a future for a future for a value (in other
words, Future<Future<x>>). In this case, the future returned by the
first .then() call waits until the outer Future from the return value
finishes, then accepts with its value, and "this value" happens to be
a Future<x>.

This sort of thing does not happen accidentally. It's hard to make
nested futures unless you're doing it on purpose, or you have no idea
what you're doing. In the first case, we want to trust the author,
and in the second case, it's probably better for everyone involved if
the code fails in a fairly obvious way, rather than attempting to
paper over the problem. If you're competent and just doing the
natural thing, the API naturally gives you plain values in callback
arguments and singly-wrapped futures in return values.

This is why I've been arguing against recursive unwrapping in the general case.

~TJ
_______________________________________________
es-discuss mailing list
es-discuss at mozilla.org
https://mail.mozilla.org/listinfo/es-discuss
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130427/9cb8f8ec/attachment-0001.html>
Tab Atkins Jr.
2013-04-27 16:47:16 UTC
Permalink
On Sat, Apr 27, 2013 at 9:21 AM, Ron Buckton <rbuckton at 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
David Sheets
2013-04-27 16:55:33 UTC
Permalink
On Sat, Apr 27, 2013 at 5:21 PM, Ron Buckton <rbuckton at chronicles.org> wrote:
> Here is a case where flattening helps:
>
> function executeAndWaitForComplete(command) {
> return getJSON(commandUrl + command)
> .then(function (commandResult) {
> if (commandResult.complete) {
> return commandResult.model;

This gets automatically lifted into Future, resulting in Future<typeof
commandResult.model>.

> }
> var statusUrl = commmandResult.statusUrl;
> return pollForCommandComplete(statusUrl);

This is always a Future and becomes the result of
executeAndWaitForComplete with type Future<typeof
commandResult.model>.

> })
> }
>
> function pollForCommandComplete(statusUrl) {
> return new Future(function(resolver) {
> var poll = function (pollResult) {
> if (pollResult.done) {
> resolve.resolve(pollResult.model);
> }
> else {
> setTimeout(function() {
> getJSON(statusUrl).done(poll, resolver.reject);
> }, 500);
> }
> }
> getJSON(statusUrl).done(poll, resolver.reject);
> });
> }
>
> 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>>.

I think the major point of confusion in these discussions is the
result of the framing of the discussion in terms of "flattening". I
believe most beneficial viewpoint is that of "autolifting".

That is, the exceptional case is not when the function argument of
"then" returns a Future+ that gets "flattened" but rather when the
function argument of "then" returns a non-Future that gets
automatically lifted into a Future.

This change in perspective is non-obvious because in many of these
APIs there is no succinct lifting operation to make a Future from
another value. This is a major reason why something like Future.of
(Future.accept) is important.

> In a non
> flattening world, the developer would have to change the function to:
>
> function executeAndWaitForComplete(command) {
> return new Future(function(resolver) {
> getJSON(commandUrl + command)
> .done(function (commandResult) {
> if (commandResult.complete) {
> resolver.resolve(commandResult.model);
> }
> var statusUrl = commmandResult.statusUrl;
> pollForCommandComplete(statusUrl).done(resolver.resolve,
> resolver.reject);
> }, resolver.reject)
> });
> }

In a non-flattening (and non-autolifting) world, this would be the code:

function executeAndWaitForComplete(command) {
return getJSON(commandUrl + command)
.then(function (commandResult) {
if (commandResult.complete) {
return Future.accept(commandResult.model);
}
var statusUrl = commmandResult.statusUrl;
return pollForCommandComplete(statusUrl);
})
}

The only difference here is that commandResult.model is explicitly
wrapped. This is because the signature of then is actually Future<a>
-> (a -> Future<b>) -> Future<b>.

David
Mark S. Miller
2013-04-27 17:05:30 UTC
Permalink
On Sat, Apr 27, 2013 at 9:55 AM, David Sheets <kosmo.zb at gmail.com> wrote:
[...]

> I think the major point of confusion in these discussions is the
> result of the framing of the discussion in terms of "flattening". I
> believe most beneficial viewpoint is that of "autolifting".
>
> That is, the exceptional case is not when the function argument of
> "then" returns a Future+ that gets "flattened" but rather when the
> function argument of "then" returns a non-Future that gets
> automatically lifted into a Future.
>
> This change in perspective is non-obvious because in many of these
> APIs there is no succinct lifting operation to make a Future from
> another value. This is a major reason why something like Future.of
> (Future.accept) is important.



I was following you until this last paragraph. As you define autolifting in
the first two paragraphs, Q(x) would be an autolifting operation. It has
the signature:

promise<t> -> promise<t>
or
t -> promise<t> // if t is not itself a promise type

Are you distinguishing "autolifting" vs "lifting"? If so, why do you think
it is important or desirable to provide a lifting operation (as opposed to
an autolifting operation)?


--
Cheers,
--MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130427/4e29f804/attachment.html>
Tab Atkins Jr.
2013-04-27 17:09:24 UTC
Permalink
On Sat, Apr 27, 2013 at 10:05 AM, Mark S. Miller <erights at google.com> wrote:
> Are you distinguishing "autolifting" vs "lifting"? If so, why do you think
> it is important or desirable to provide a lifting operation (as opposed to
> an autolifting operation)?

Because the "lifting" operation is the monadic lifting operation,
which you need if you want to write monadic code that works
predictably. If all you have is an auto-lifter, your code will
randomly fail sometimes in mysterious ways, because you're violating
the monad laws. (In a distinct, though thematically similar, way to
how your code sometimes mysteriously fails if you use the Array
constructor instead of Array.of().)

~TJ
Mark Miller
2013-04-27 17:20:37 UTC
Permalink
Sorry, I've been writing code with E style promises for, jeez, over 20
years now. (I suddenly feel very old :( .) I don't remember ever
experiencing the failure you're talking about. Can you give a concrete
example?


On Sat, Apr 27, 2013 at 10:09 AM, Tab Atkins Jr. <jackalmage at gmail.com>wrote:

> On Sat, Apr 27, 2013 at 10:05 AM, Mark S. Miller <erights at google.com>
> wrote:
> > Are you distinguishing "autolifting" vs "lifting"? If so, why do you
> think
> > it is important or desirable to provide a lifting operation (as opposed
> to
> > an autolifting operation)?
>
> Because the "lifting" operation is the monadic lifting operation,
> which you need if you want to write monadic code that works
> predictably. If all you have is an auto-lifter, your code will
> randomly fail sometimes in mysterious ways, because you're violating
> the monad laws. (In a distinct, though thematically similar, way to
> how your code sometimes mysteriously fails if you use the Array
> constructor instead of Array.of().)
>
> ~TJ
>



--
Text by me above is hereby placed in the public domain

Cheers,
--MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130427/ce97df6e/attachment-0001.html>
Tab Atkins Jr.
2013-04-27 18:07:11 UTC
Permalink
On Sat, Apr 27, 2013 at 10:20 AM, Mark Miller <erights at gmail.com> wrote:
> Sorry, I've been writing code with E style promises for, jeez, over 20 years
> now. (I suddenly feel very old :( .) I don't remember ever experiencing the
> failure you're talking about. Can you give a concrete example?

E-style promises aren't monadic, so you'd never have the chance to use
them with generic monadic libraries. ^_^

~TJ
David Sheets
2013-04-27 18:07:07 UTC
Permalink
On Sat, Apr 27, 2013 at 6:05 PM, Mark S. Miller <erights at google.com> wrote:
> On Sat, Apr 27, 2013 at 9:55 AM, David Sheets <kosmo.zb at gmail.com> wrote:
> [...]
>>
>> I think the major point of confusion in these discussions is the
>> result of the framing of the discussion in terms of "flattening". I
>> believe most beneficial viewpoint is that of "autolifting".
>>
>> That is, the exceptional case is not when the function argument of
>> "then" returns a Future+ that gets "flattened" but rather when the
>> function argument of "then" returns a non-Future that gets
>> automatically lifted into a Future.
>>
>> This change in perspective is non-obvious because in many of these
>> APIs there is no succinct lifting operation to make a Future from
>> another value. This is a major reason why something like Future.of
>> (Future.accept) is important.
>
> I was following you until this last paragraph. As you define autolifting in
> the first two paragraphs, Q(x) would be an autolifting operation. It has the
> signature:
>
> promise<t> -> promise<t>
> or
> t -> promise<t> // if t is not itself a promise type
>
> Are you distinguishing "autolifting" vs "lifting"? If so, why do you think
> it is important or desirable to provide a lifting operation (as opposed to
> an autolifting operation)?

Yes. Autolifting is conditional on promise-ness. Lifting is fully parametric.

If the standard uses autolifting instead of recursive flattening, many
of the headaches with "thenables" go away and we gain enormous
flexibility in future interoperation with the spec.

For instance, if your code might manipulate objects which have
callable "then" fields but which don't subscribe to the promises spec,
it is safest to always use:

return Promise.of(myMaybeNonPromiseThenable);

This greatly reduces the criticality of the "is this a promise?"
predicate because in most cases you will simply return a non-thenable
(autolifted) or a promise-like thenable and not care. In those cases
where you wish to put non-promise thenable inside of a promise or
*don't know if someone else will want to*, the explicit use of the
lifting operation lets you avoid autolifting/flattening.

This massively simplifies the protocol between the promises spec and
those values it encapsulates by only ever making a single assumption
that then-returned thenables are promise-like but their contents are
*totally opaque*.

I believe this design results in the maximal flexibility and safety
for the platform by supplying a handy autolifting "then" while also
allowing people to easily subscribe to promise interaction (by
then-returning a thenable), defend their thenables from magical
unwrapping (by explicitly using Promise.of), and write completely
polymorphic code.

With this design, in the most common case, developers won't have to
use Promise.of. Perhaps the only common use will be in starting a
promise chain from a constant:

var promise;
if (just_use_a_constant) { promise = Promise.of(6); } else { promise =
getAsyncValue(); }
promise.then(function (x) { return x*2); });

While those who translate code from other languages, target their
compilers to JS Promises, write polymorphic libraries, use
non-promises with callable "then" fields, or invent new interoperable
promise-like (but distinct) semantics won't have to worry about
hacking around recursive unwrapping.

To me, having some standard for promise-like objects in the platform
seems very fundamental for handling asynchrony, ordering, failure,
need, and probability. If we consider promise-like objects as
fundamental, we should investigate the properties of their operations:

With recursive flattening "then" operation, the time complexity of
"then" is O(n) with n the number of nested promise-like objects.
With autolifted "then" operation, the time complexity of "then" is O(1).

Here, I am using time complexity as a proxy for the mental complexity
of the operation and not as a proxy for execution performance
(recursive unwrapping is usually of static depth as we have seen). You
can see that not only does the recursive flattening involve a hidden
loop that the advanced programmer must reason about but also invokes
the notion of "promise-like object" which, as we have seen, leads to
all sorts of tangents regarding how to characterize the property of
promise-ness and still maintain clarity, safety, extensibility, and
ease-of-use.

I hope this explanation satisfies you. If not, I am more than happy to
answer any questions you may have about this approach.

Warm regards,

David Sheets
Mark Miller
2013-04-28 00:46:46 UTC
Permalink
I am worried that we're again separated by a common terminology more than
we are by an actual technical disagreement. I am arguing against an
unconditional lift operation that would make a promise-for-promise. Or at
least seeking to provoke someone to provide a compelling example showing
why this is useful[1]. What all the recent messages seem to be arguing for
is the existence of a non-assimilating and perhaps a non-unwrapping
lift-ish operation, so that one can make a promise-for-thenable. I have no
objection to being able to make a promise-for-thenable.

The clearest sign to me of the potential for misunderstanding is the use of
the term "flattening" for unwrapping of thenables. To be clear,
"flattening" is about promises -- it is just the conditional autolifting
seen from the other side (as I now understand the term autolifting --
thanks). "unwrapping" is what happens to thenables. "Assimilation" is
recursive unwrapping of thenables. I understand that it can be difficult to
keep these distinctions straight. I wouldn't be surprised if I've been
sloppy myself with these terms earlier in these threads. But now that we're
zeroing in, these fine distinctions matter.

As I demonstrated somewhere in one of the Promises/A+ threads, I don't
think it is practical to prohibit these promises-for-thenables anyway. As
an example, let's take Q's use of Q as an allegedly fully assimilating
lift-ish function. Like an autolifter Q(promise) returns that promise. And
Q(x) where x is neither a promise nor a thenable, returns promise-for-x.
The controversial part -- which I fully sympathize with since it is a dirty
hack -- is that Q(thenable) assimilates -- it does recursive unwrapping
(NOT "flattening") of the thenable. Assimilation aside, Q is an autolifter,
so I'll can it an "assimilating autolifter". Assume a system is which this
Q function and .then are the only lift-ish operations, and that .then is
also an assimilating autolifter. What guarantee is supposed to follow?

Assuming that the thenable check is if(typeof x.then === 'function'),
intuitively, we might think that

Q(p).then(shouldntBeThenable => alert(typeof shouldntBeThenable.then));

should never alert 'function'. But this guarantee does not follow:

var p = {};
Q(p).then(shouldntBeThenable => alert(typeof shouldntBeThenable.then));
p.then = function() { return "gotcha"; };

This is a consequence of assimilation being a dirty hack. The notion of a
thenable is only marginally more principled than the notion of array-like.
It is not a stable property. Above, p became a thenable after Q already
judged it to be a non-promise non-thenable and hence returned a
promise-for-p. This promise scheduled a call to the callback before p
became a thenable, but p became a thenable before the callback got called.
Alleged guarantee broken.

Thus, no fundamental guarantee would be lost by introducing an expert-only
operation that does autolifting but no unwrapping. This would make
convenient what is possible anyway: promises-for-thenables. But this is
*not* an argument for introducing a full lifting operation. Introducing
that would break an important guarantee -- that there are no
promises-for-promises. Without full lifting, promises-for-promises remain
impossible.

I leave it to monad fans and/or haters of assimilation to suggest names for
this convenient operation, a non-unwrapping autolifter. I'm confident that
if I tried to name it, I'd only cause more confusion ;).


[1] FWIW, if there's interest, I can provide several examples where a
promise-for-promise is useful, but I know of no compelling example. The
utility of the examples I have in mind are not worth the cost of
introducing this possibility of a promise-for-promise.




On Sat, Apr 27, 2013 at 11:07 AM, David Sheets <kosmo.zb at gmail.com> wrote:

> On Sat, Apr 27, 2013 at 6:05 PM, Mark S. Miller <erights at google.com>
> wrote:
> > On Sat, Apr 27, 2013 at 9:55 AM, David Sheets <kosmo.zb at gmail.com>
> wrote:
> > [...]
> >>
> >> I think the major point of confusion in these discussions is the
> >> result of the framing of the discussion in terms of "flattening". I
> >> believe most beneficial viewpoint is that of "autolifting".
> >>
> >> That is, the exceptional case is not when the function argument of
> >> "then" returns a Future+ that gets "flattened" but rather when the
> >> function argument of "then" returns a non-Future that gets
> >> automatically lifted into a Future.
> >>
> >> This change in perspective is non-obvious because in many of these
> >> APIs there is no succinct lifting operation to make a Future from
> >> another value. This is a major reason why something like Future.of
> >> (Future.accept) is important.
> >
> > I was following you until this last paragraph. As you define autolifting
> in
> > the first two paragraphs, Q(x) would be an autolifting operation. It has
> the
> > signature:
> >
> > promise<t> -> promise<t>
> > or
> > t -> promise<t> // if t is not itself a promise type
> >
> > Are you distinguishing "autolifting" vs "lifting"? If so, why do you
> think
> > it is important or desirable to provide a lifting operation (as opposed
> to
> > an autolifting operation)?
>
> Yes. Autolifting is conditional on promise-ness. Lifting is fully
> parametric.
>
> If the standard uses autolifting instead of recursive flattening, many
> of the headaches with "thenables" go away and we gain enormous
> flexibility in future interoperation with the spec.
>
> For instance, if your code might manipulate objects which have
> callable "then" fields but which don't subscribe to the promises spec,
> it is safest to always use:
>
> return Promise.of(myMaybeNonPromiseThenable);
>
> This greatly reduces the criticality of the "is this a promise?"
> predicate because in most cases you will simply return a non-thenable
> (autolifted) or a promise-like thenable and not care. In those cases
> where you wish to put non-promise thenable inside of a promise or
> *don't know if someone else will want to*, the explicit use of the
> lifting operation lets you avoid autolifting/flattening.
>
> This massively simplifies the protocol between the promises spec and
> those values it encapsulates by only ever making a single assumption
> that then-returned thenables are promise-like but their contents are
> *totally opaque*.
>
> I believe this design results in the maximal flexibility and safety
> for the platform by supplying a handy autolifting "then" while also
> allowing people to easily subscribe to promise interaction (by
> then-returning a thenable), defend their thenables from magical
> unwrapping (by explicitly using Promise.of), and write completely
> polymorphic code.
>
> With this design, in the most common case, developers won't have to
> use Promise.of. Perhaps the only common use will be in starting a
> promise chain from a constant:
>
> var promise;
> if (just_use_a_constant) { promise = Promise.of(6); } else { promise =
> getAsyncValue(); }
> promise.then(function (x) { return x*2); });
>
> While those who translate code from other languages, target their
> compilers to JS Promises, write polymorphic libraries, use
> non-promises with callable "then" fields, or invent new interoperable
> promise-like (but distinct) semantics won't have to worry about
> hacking around recursive unwrapping.
>
> To me, having some standard for promise-like objects in the platform
> seems very fundamental for handling asynchrony, ordering, failure,
> need, and probability. If we consider promise-like objects as
> fundamental, we should investigate the properties of their operations:
>
> With recursive flattening "then" operation, the time complexity of
> "then" is O(n) with n the number of nested promise-like objects.
> With autolifted "then" operation, the time complexity of "then" is O(1).
>
> Here, I am using time complexity as a proxy for the mental complexity
> of the operation and not as a proxy for execution performance
> (recursive unwrapping is usually of static depth as we have seen). You
> can see that not only does the recursive flattening involve a hidden
> loop that the advanced programmer must reason about but also invokes
> the notion of "promise-like object" which, as we have seen, leads to
> all sorts of tangents regarding how to characterize the property of
> promise-ness and still maintain clarity, safety, extensibility, and
> ease-of-use.
>
> I hope this explanation satisfies you. If not, I am more than happy to
> answer any questions you may have about this approach.
>
> Warm regards,
>
> David Sheets
>



--
Text by me above is hereby placed in the public domain

Cheers,
--MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130427/367ee7f2/attachment-0001.html>
Mark S. Miller
2013-04-28 01:15:44 UTC
Permalink
On Sat, Apr 27, 2013 at 5:46 PM, Mark Miller <erights at gmail.com> wrote:

> I am worried that we're again separated by a common terminology more than
> we are by an actual technical disagreement. I am arguing against an
> unconditional lift operation that would make a promise-for-promise. Or at
> least seeking to provoke someone to provide a compelling example showing
> why this is useful[1]. What all the recent messages seem to be arguing for
> is the existence of a non-assimilating and perhaps a non-unwrapping
> lift-ish operation, so that one can make a promise-for-thenable. I have no
> objection to being able to make a promise-for-thenable.
>
> The clearest sign to me of the potential for misunderstanding is the use
> of the term "flattening" for unwrapping of thenables. To be clear,
> "flattening" is about promises -- it is just the conditional autolifting
> seen from the other side (as I now understand the term autolifting --
> thanks). "unwrapping" is what happens to thenables. "Assimilation" is
> recursive unwrapping of thenables. I understand that it can be difficult to
> keep these distinctions straight. I wouldn't be surprised if I've been
> sloppy myself with these terms earlier in these threads. But now that we're
> zeroing in, these fine distinctions matter.
>
> As I demonstrated somewhere in one of the Promises/A+ threads, I don't
> think it is practical to prohibit these promises-for-thenables anyway. As
> an example, let's take Q's use of Q as an allegedly fully assimilating
> lift-ish function. Like an autolifter Q(promise) returns that promise. And
> Q(x) where x is neither a promise nor a thenable, returns promise-for-x.
> The controversial part -- which I fully sympathize with since it is a dirty
> hack -- is that Q(thenable) assimilates -- it does recursive unwrapping
> (NOT "flattening") of the thenable. Assimilation aside, Q is an autolifter,
> so I'll can it an "assimilating autolifter".
>

Should be: '...so I'll *call* it an "assimilating autolifter".'.




> Assume a system is which this Q function and .then are the only lift-ish
> operations, and that .then is also an assimilating autolifter. What
> guarantee is supposed to follow?
>
> Assuming that the thenable check is if(typeof x.then === 'function'),
> intuitively, we might think that
>
> Q(p).then(shouldntBeThenable => alert(typeof shouldntBeThenable.then));
>
> should never alert 'function'. But this guarantee does not follow:
>
> var p = {};
> Q(p).then(shouldntBeThenable => alert(typeof shouldntBeThenable.then));
> p.then = function() { return "gotcha"; };
>
> This is a consequence of assimilation being a dirty hack. The notion of a
> thenable is only marginally more principled than the notion of array-like.
> It is not a stable property. Above, p became a thenable after Q already
> judged it to be a non-promise non-thenable and hence returned a
> promise-for-p. This promise scheduled a call to the callback before p
> became a thenable, but p became a thenable before the callback got called.
> Alleged guarantee broken.
>
> Thus, no fundamental guarantee would be lost by introducing an expert-only
> operation that does autolifting but no unwrapping. This would make
> convenient what is possible anyway: promises-for-thenables. But this is
> *not* an argument for introducing a full lifting operation. Introducing
> that would break an important guarantee -- that there are no
> promises-for-promises. Without full lifting, promises-for-promises remain
> impossible.
>
> I leave it to monad fans and/or haters of assimilation to suggest names
> for this convenient operation, a non-unwrapping autolifter. I'm confident
> that if I tried to name it, I'd only cause more confusion ;).
>
>
> [1] FWIW, if there's interest, I can provide several examples where a
> promise-for-promise is useful, but I know of no compelling example. The
> utility of the examples I have in mind are not worth the cost of
> introducing this possibility of a promise-for-promise.
>
>
>
>
> On Sat, Apr 27, 2013 at 11:07 AM, David Sheets <kosmo.zb at gmail.com> wrote:
>
>> On Sat, Apr 27, 2013 at 6:05 PM, Mark S. Miller <erights at google.com>
>> wrote:
>> > On Sat, Apr 27, 2013 at 9:55 AM, David Sheets <kosmo.zb at gmail.com>
>> wrote:
>> > [...]
>> >>
>> >> I think the major point of confusion in these discussions is the
>> >> result of the framing of the discussion in terms of "flattening". I
>> >> believe most beneficial viewpoint is that of "autolifting".
>> >>
>> >> That is, the exceptional case is not when the function argument of
>> >> "then" returns a Future+ that gets "flattened" but rather when the
>> >> function argument of "then" returns a non-Future that gets
>> >> automatically lifted into a Future.
>> >>
>> >> This change in perspective is non-obvious because in many of these
>> >> APIs there is no succinct lifting operation to make a Future from
>> >> another value. This is a major reason why something like Future.of
>> >> (Future.accept) is important.
>> >
>> > I was following you until this last paragraph. As you define
>> autolifting in
>> > the first two paragraphs, Q(x) would be an autolifting operation. It
>> has the
>> > signature:
>> >
>> > promise<t> -> promise<t>
>> > or
>> > t -> promise<t> // if t is not itself a promise type
>> >
>> > Are you distinguishing "autolifting" vs "lifting"? If so, why do you
>> think
>> > it is important or desirable to provide a lifting operation (as opposed
>> to
>> > an autolifting operation)?
>>
>> Yes. Autolifting is conditional on promise-ness. Lifting is fully
>> parametric.
>>
>> If the standard uses autolifting instead of recursive flattening, many
>> of the headaches with "thenables" go away and we gain enormous
>> flexibility in future interoperation with the spec.
>>
>> For instance, if your code might manipulate objects which have
>> callable "then" fields but which don't subscribe to the promises spec,
>> it is safest to always use:
>>
>> return Promise.of(myMaybeNonPromiseThenable);
>>
>> This greatly reduces the criticality of the "is this a promise?"
>> predicate because in most cases you will simply return a non-thenable
>> (autolifted) or a promise-like thenable and not care. In those cases
>> where you wish to put non-promise thenable inside of a promise or
>> *don't know if someone else will want to*, the explicit use of the
>> lifting operation lets you avoid autolifting/flattening.
>>
>> This massively simplifies the protocol between the promises spec and
>> those values it encapsulates by only ever making a single assumption
>> that then-returned thenables are promise-like but their contents are
>> *totally opaque*.
>>
>> I believe this design results in the maximal flexibility and safety
>> for the platform by supplying a handy autolifting "then" while also
>> allowing people to easily subscribe to promise interaction (by
>> then-returning a thenable), defend their thenables from magical
>> unwrapping (by explicitly using Promise.of), and write completely
>> polymorphic code.
>>
>> With this design, in the most common case, developers won't have to
>> use Promise.of. Perhaps the only common use will be in starting a
>> promise chain from a constant:
>>
>> var promise;
>> if (just_use_a_constant) { promise = Promise.of(6); } else { promise =
>> getAsyncValue(); }
>> promise.then(function (x) { return x*2); });
>>
>> While those who translate code from other languages, target their
>> compilers to JS Promises, write polymorphic libraries, use
>> non-promises with callable "then" fields, or invent new interoperable
>> promise-like (but distinct) semantics won't have to worry about
>> hacking around recursive unwrapping.
>>
>> To me, having some standard for promise-like objects in the platform
>> seems very fundamental for handling asynchrony, ordering, failure,
>> need, and probability. If we consider promise-like objects as
>> fundamental, we should investigate the properties of their operations:
>>
>> With recursive flattening "then" operation, the time complexity of
>> "then" is O(n) with n the number of nested promise-like objects.
>> With autolifted "then" operation, the time complexity of "then" is O(1).
>>
>> Here, I am using time complexity as a proxy for the mental complexity
>> of the operation and not as a proxy for execution performance
>> (recursive unwrapping is usually of static depth as we have seen). You
>> can see that not only does the recursive flattening involve a hidden
>> loop that the advanced programmer must reason about but also invokes
>> the notion of "promise-like object" which, as we have seen, leads to
>> all sorts of tangents regarding how to characterize the property of
>> promise-ness and still maintain clarity, safety, extensibility, and
>> ease-of-use.
>>
>> I hope this explanation satisfies you. If not, I am more than happy to
>> answer any questions you may have about this approach.
>>
>> Warm regards,
>>
>> David Sheets
>>
>
>
>
> --
> Text by me above is hereby placed in the public domain
>
> Cheers,
> --MarkM
>



--
Cheers,
--MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130427/42dcc833/attachment-0001.html>
David Sheets
2013-04-28 02:41:22 UTC
Permalink
On Sun, Apr 28, 2013 at 1:46 AM, Mark Miller <erights at gmail.com> wrote:
> I am worried that we're again separated by a common terminology more than we
> are by an actual technical disagreement. I am arguing against an
> unconditional lift operation that would make a promise-for-promise. Or at
> least seeking to provoke someone to provide a compelling example showing why
> this is useful[1]. What all the recent messages seem to be arguing for is
> the existence of a non-assimilating and perhaps a non-unwrapping lift-ish
> operation, so that one can make a promise-for-thenable. I have no objection
> to being able to make a promise-for-thenable.

But promises aren't thenable? Why are they special?

> The clearest sign to me of the potential for misunderstanding is the use of
> the term "flattening" for unwrapping of thenables. To be clear, "flattening"
> is about promises -- it is just the conditional autolifting seen from the
> other side (as I now understand the term autolifting -- thanks).

If ((
f : a -> b => a -> Promise<b> if b != Promise<c> for all c
but
f : a -> b => a -> b if b = Promise<c> for all c
) for all f under "then")
is "autolifting"

Then ((
f : a -> Promise<b> => a -> Promise<b> if b != Promise<c> for all c
but
f : a -> Promise<b> => a -> Promise<c> if b = Promise<c> for all c
) for all f under "then")
is "autojoining"

Because "join" : M M t -> M t

(properly, "autolifting" should convert a -> b into Promise<a> ->
Promise<b> and Promise<a> -> Promise<b> into Promise<a> -> Promise<b>
but we can probably ignore this earlier mis-take I made so long as we
can agree on the behavior; "return autoboxing" might have been a
better terminological choice)

> "unwrapping" is what happens to thenables. "Assimilation" is recursive
> unwrapping of thenables. I understand that it can be difficult to keep these
> distinctions straight. I wouldn't be surprised if I've been sloppy myself
> with these terms earlier in these threads. But now that we're zeroing in,
> these fine distinctions matter.

I agree. We should adopt precise definitions to avoid further
confusion. To that end, I propose we formally define "flattening" to
be recursively autojoining to a fixpoint.

That is,

assimilation : thenable :: flattening : promise

This is because flattening implies things are, well, flat (one-level
and no more).

> As I demonstrated somewhere in one of the Promises/A+ threads, I don't think
> it is practical to prohibit these promises-for-thenables anyway. As an
> example, let's take Q's use of Q as an allegedly fully assimilating lift-ish
> function. Like an autolifter Q(promise) returns that promise. And Q(x) where
> x is neither a promise nor a thenable, returns promise-for-x. The
> controversial part -- which I fully sympathize with since it is a dirty hack
> -- is that Q(thenable) assimilates -- it does recursive unwrapping (NOT
> "flattening") of the thenable. Assimilation aside, Q is an autolifter, so
> I'll can it an "assimilating autolifter". Assume a system is which this Q
> function and .then are the only lift-ish operations, and that .then is also
> an assimilating autolifter. What guarantee is supposed to follow?

Let's use P<t> for the promise type constructor and T<t> for the
thenable type constructor.

Suppose we evaulate y = Q(x : P<T<P<T<P<t>>>>>). [which should really
never happen but ignore that, this is for edification]

Does Q simultaneously flatten and assimilate its argument to a
fixpoint? Afaict, yes, because promises are a subset of thenables. Is
y : P<t>?

I agree that Q is also overloaded as an autolifter such that Q : t ->
P<t> for simple t and Q : P<t> -> P<t> for simple t.

> Assuming that the thenable check is if(typeof x.then === 'function'),
> intuitively, we might think that
>
> Q(p).then(shouldntBeThenable => alert(typeof shouldntBeThenable.then));
>
> should never alert 'function'. But this guarantee does not follow:
>
> var p = {};
> Q(p).then(shouldntBeThenable => alert(typeof shouldntBeThenable.then));
> p.then = function() { return "gotcha"; };
>
> This is a consequence of assimilation being a dirty hack. The notion of a
> thenable is only marginally more principled than the notion of array-like.
> It is not a stable property. Above, p became a thenable after Q already
> judged it to be a non-promise non-thenable and hence returned a
> promise-for-p. This promise scheduled a call to the callback before p became
> a thenable, but p became a thenable before the callback got called. Alleged
> guarantee broken.
>
> Thus, no fundamental guarantee would be lost by introducing an expert-only
> operation that does autolifting but no unwrapping.

To do autolifting, you must have a predicate that distinguishes P<t> from T<t>.

> This would make
> convenient what is possible anyway: promises-for-thenables. But this is
> *not* an argument for introducing a full lifting operation. Introducing that
> would break an important guarantee -- that there are no
> promises-for-promises. Without full lifting, promises-for-promises remain
> impossible.

This assumes that promises are somehow special and beyond construction
by the programmer. This immaculate distinction troubles me because it
immediately closes off the ability to construct systems which are
peers to promises. That is,

promise.then(x => return unblessedPromise()).then(y =>
alert(isUnblessedPromise(y)))

alerts "true" because unBlessedPromise() was lifted into
P<UnblessedPromise<t>> instead of being helpfully unwrapped and then
lifted into Promise<t>. (I should note here that dynamically
dispatching the subsequent "then" to UnblessedPromise<t> is an
interesting possible design decision and probably necessary to
maintain associativity. This could be overridden by return
unblessedPromise().then(Promise.of);)

That is the behavior of the above is identical to the behavior of

promise.then(x => return Promise.of(unblessedPromise())).then(y =>
alert(isUnblessedPromise(y)))

which is quite disappointing.

Ideally, any standardization of this facility would define a simple
object protocol and a set of equivalences of operations over objects
with types in that protocol's class. If a promise system which
seamlessly interoperates with Official Promises cannot be constructed
by a programmer (no object I can construct is true for the isPromise
predicate), we will have lost a great deal. If this occurs, any
JavaScript environment which enforces this blessed/unblessed
distinction will no longer allow developers to construct many
important libraries and compatible variations of promises (e.g. I want
to centrally and conditionally enable logging for all promise
fulfillments).

If you allow interoperability, then it must be decided how to signal
what it *is* to *be* a promise and all promises must exhibit certain
properties. In fact, I would go so far as to say that any /true/
standard should define both the contract of a promise-able and the
specific semantics that its flavor of promise-able implements.

>From this point of view, it is impossible to mandate no
promises-for-promises as there is, by definition, no way to tell if a
given promise is yours or mine. This is an important invariant and
crucial for the future health and extensibility of this feature.

Whether adherence to that contract occurs by checking
isCallable(x.then) or in some other manner, it does not matter (except
for compatibility with existing libraries which use that predicate as
typeclass advertisement). In all circumstances, no recursive
operations over promises or thenables should be mandated. Autojoining
and autolifting are OK and, as you observe, lifting and joining cannot
truly be prohibited. Assimilation or flattening are useful
operation(s) when explicitly invoked; however, it should not occur in
regular use of a promise.

I feel we have made great progress. I hope it is clear now that no
built-in (automatically in, e.g., "then") unwrapping or assimilation
should occur. If that is settled, the only remaining issue is over the
signal to indicate that something is a promise whether built-in or
user-defined.

Best wishes,

David
David Sheets
2013-04-28 02:47:45 UTC
Permalink
On Sun, Apr 28, 2013 at 3:41 AM, David Sheets <kosmo.zb at gmail.com> wrote:
> On Sun, Apr 28, 2013 at 1:46 AM, Mark Miller <erights at gmail.com> wrote:
>> I am worried that we're again separated by a common terminology more than we
>> are by an actual technical disagreement. I am arguing against an
>> unconditional lift operation that would make a promise-for-promise. Or at
>> least seeking to provoke someone to provide a compelling example showing why
>> this is useful[1]. What all the recent messages seem to be arguing for is
>> the existence of a non-assimilating and perhaps a non-unwrapping lift-ish
>> operation, so that one can make a promise-for-thenable. I have no objection
>> to being able to make a promise-for-thenable.
>
> But promises aren't thenable? Why are they special?
>
>> The clearest sign to me of the potential for misunderstanding is the use of
>> the term "flattening" for unwrapping of thenables. To be clear, "flattening"
>> is about promises -- it is just the conditional autolifting seen from the
>> other side (as I now understand the term autolifting -- thanks).
>
> If ((
> f : a -> b => a -> Promise<b> if b != Promise<c> for all c
> but
> f : a -> b => a -> b if b = Promise<c> for all c
> ) for all f under "then")
> is "autolifting"
>
> Then ((
> f : a -> Promise<b> => a -> Promise<b> if b != Promise<c> for all c
> but
> f : a -> Promise<b> => a -> Promise<c> if b = Promise<c> for all c
> ) for all f under "then")
> is "autojoining"
>
> Because "join" : M M t -> M t
>
> (properly, "autolifting" should convert a -> b into Promise<a> ->
> Promise<b> and Promise<a> -> Promise<b> into Promise<a> -> Promise<b>
> but we can probably ignore this earlier mis-take I made so long as we
> can agree on the behavior; "return autoboxing" might have been a
> better terminological choice)
>
>> "unwrapping" is what happens to thenables. "Assimilation" is recursive
>> unwrapping of thenables. I understand that it can be difficult to keep these
>> distinctions straight. I wouldn't be surprised if I've been sloppy myself
>> with these terms earlier in these threads. But now that we're zeroing in,
>> these fine distinctions matter.
>
> I agree. We should adopt precise definitions to avoid further
> confusion. To that end, I propose we formally define "flattening" to
> be recursively autojoining to a fixpoint.
>
> That is,
>
> assimilation : thenable :: flattening : promise
>
> This is because flattening implies things are, well, flat (one-level
> and no more).
>
>> As I demonstrated somewhere in one of the Promises/A+ threads, I don't think
>> it is practical to prohibit these promises-for-thenables anyway. As an
>> example, let's take Q's use of Q as an allegedly fully assimilating lift-ish
>> function. Like an autolifter Q(promise) returns that promise. And Q(x) where
>> x is neither a promise nor a thenable, returns promise-for-x. The
>> controversial part -- which I fully sympathize with since it is a dirty hack
>> -- is that Q(thenable) assimilates -- it does recursive unwrapping (NOT
>> "flattening") of the thenable. Assimilation aside, Q is an autolifter, so
>> I'll can it an "assimilating autolifter". Assume a system is which this Q
>> function and .then are the only lift-ish operations, and that .then is also
>> an assimilating autolifter. What guarantee is supposed to follow?
>
> Let's use P<t> for the promise type constructor and T<t> for the
> thenable type constructor.
>
> Suppose we evaulate y = Q(x : P<T<P<T<P<t>>>>>). [which should really
> never happen but ignore that, this is for edification]
>
> Does Q simultaneously flatten and assimilate its argument to a
> fixpoint? Afaict, yes, because promises are a subset of thenables. Is
> y : P<t>?
>
> I agree that Q is also overloaded as an autolifter such that Q : t ->
> P<t> for simple t and Q : P<t> -> P<t> for simple t.
>
>> Assuming that the thenable check is if(typeof x.then === 'function'),
>> intuitively, we might think that
>>
>> Q(p).then(shouldntBeThenable => alert(typeof shouldntBeThenable.then));
>>
>> should never alert 'function'. But this guarantee does not follow:
>>
>> var p = {};
>> Q(p).then(shouldntBeThenable => alert(typeof shouldntBeThenable.then));
>> p.then = function() { return "gotcha"; };
>>
>> This is a consequence of assimilation being a dirty hack. The notion of a
>> thenable is only marginally more principled than the notion of array-like.
>> It is not a stable property. Above, p became a thenable after Q already
>> judged it to be a non-promise non-thenable and hence returned a
>> promise-for-p. This promise scheduled a call to the callback before p became
>> a thenable, but p became a thenable before the callback got called. Alleged
>> guarantee broken.
>>
>> Thus, no fundamental guarantee would be lost by introducing an expert-only
>> operation that does autolifting but no unwrapping.
>
> To do autolifting, you must have a predicate that distinguishes P<t> from T<t>.
>
>> This would make
>> convenient what is possible anyway: promises-for-thenables. But this is
>> *not* an argument for introducing a full lifting operation. Introducing that
>> would break an important guarantee -- that there are no
>> promises-for-promises. Without full lifting, promises-for-promises remain
>> impossible.
>
> This assumes that promises are somehow special and beyond construction
> by the programmer. This immaculate distinction troubles me because it
> immediately closes off the ability to construct systems which are
> peers to promises. That is,
>
> promise.then(x => return unblessedPromise()).then(y =>
> alert(isUnblessedPromise(y)))
>
> alerts "true" because unBlessedPromise() was lifted into
> P<UnblessedPromise<t>> instead of being helpfully unwrapped and then
> lifted into Promise<t>. (I should note here that dynamically
> dispatching the subsequent "then" to UnblessedPromise<t> is an
> interesting possible design decision and probably necessary to
> maintain associativity. This could be overridden by return
> unblessedPromise().then(Promise.of);)
>
> That is the behavior of the above is identical to the behavior of
>
> promise.then(x => return Promise.of(unblessedPromise())).then(y =>
> alert(isUnblessedPromise(y)))
>
> which is quite disappointing.
>
> Ideally, any standardization of this facility would define a simple
> object protocol and a set of equivalences of operations over objects
> with types in that protocol's class. If a promise system which
> seamlessly interoperates with Official Promises cannot be constructed
> by a programmer (no object I can construct is true for the isPromise
> predicate), we will have lost a great deal. If this occurs, any
> JavaScript environment which enforces this blessed/unblessed
> distinction will no longer allow developers to construct many
> important libraries and compatible variations of promises (e.g. I want
> to centrally and conditionally enable logging for all promise
> fulfillments).
>
> If you allow interoperability, then it must be decided how to signal
> what it *is* to *be* a promise and all promises must exhibit certain
> properties. In fact, I would go so far as to say that any /true/
> standard should define both the contract of a promise-able and the
> specific semantics that its flavor of promise-able implements.
>
> From this point of view, it is impossible to mandate no
> promises-for-promises as there is, by definition, no way to tell if a
> given promise is yours or mine. This is an important invariant and
> crucial for the future health and extensibility of this feature.
>
> Whether adherence to that contract occurs by checking
> isCallable(x.then) or in some other manner, it does not matter (except
> for compatibility with existing libraries which use that predicate as
> typeclass advertisement). In all circumstances, no recursive
> operations over promises or thenables should be mandated. Autojoining
> and autolifting are OK and, as you observe, lifting and joining cannot
> truly be prohibited. Assimilation or flattening are useful
> operation(s) when explicitly invoked; however, it should not occur in
> regular use of a promise.
>
> I feel we have made great progress. I hope it is clear now that no
> built-in (automatically in, e.g., "then") unwrapping or assimilation

s/unwrapping or assimilation/flattening or assimilation/ of course.

> should occur. If that is settled, the only remaining issue is over the
> signal to indicate that something is a promise whether built-in or
> user-defined.
>
> Best wishes,
>
> David
Tab Atkins Jr.
2013-04-28 03:31:45 UTC
Permalink
On Sat, Apr 27, 2013 at 5:46 PM, Mark Miller <erights at gmail.com> wrote:
> I am worried that we're again separated by a common terminology more than we
> are by an actual technical disagreement. I am arguing against an
> unconditional lift operation that would make a promise-for-promise. Or at
> least seeking to provoke someone to provide a compelling example showing why
> this is useful[1]. What all the recent messages seem to be arguing for is
> the existence of a non-assimilating and perhaps a non-unwrapping lift-ish
> operation, so that one can make a promise-for-thenable. I have no objection
> to being able to make a promise-for-thenable.

The reasoning *for* an unconditional lift operator is that promises,
as specified by DOM Futures or Promises/A+, are roughly a monad, and
having an unconditional lift is required to satisfy the monad laws.
Being a monad is useful, in ways similar to how being an iterable is
useful, or an array-like, or many other typeclasses that various
languages recognize.

For example, Future.all() is almost equivalent to a variadic liftA()
(name taken from Haskell), which takes a function and any number of
arguments, all wrapped in some particular monad, and executes the
function with its arguments in the way that the monad prefers. For
promises, that means "if every promise accepts, execute the function
and return an accepted promise for the return value; otherwise, return
a rejected promise". This sort of function comes *for free* once a
class has been established as a monad, just as Python's very useful
itertools library applies "for free" to anything that establishes
itself as an iterable. You don't have to write any special code to
make it happen; just the fact that promises are monads means that the
generically-written liftA() method automatically works.

> The clearest sign to me of the potential for misunderstanding is the use of
> the term "flattening" for unwrapping of thenables. To be clear, "flattening"
> is about promises -- it is just the conditional autolifting seen from the
> other side (as I now understand the term autolifting -- thanks).
> "unwrapping" is what happens to thenables. "Assimilation" is recursive
> unwrapping of thenables. I understand that it can be difficult to keep these
> distinctions straight. I wouldn't be surprised if I've been sloppy myself
> with these terms earlier in these threads. But now that we're zeroing in,
> these fine distinctions matter.

I'm not sure where you got this precise terminology, but I'm willing
to adopt it for these discussions if it helps reduce confusion.

> As I demonstrated somewhere in one of the Promises/A+ threads, I don't think
> it is practical to prohibit these promises-for-thenables anyway. As an
> example, let's take Q's use of Q as an allegedly fully assimilating lift-ish
> function. Like an autolifter Q(promise) returns that promise. And Q(x) where
> x is neither a promise nor a thenable, returns promise-for-x. The
> controversial part -- which I fully sympathize with since it is a dirty hack
> -- is that Q(thenable) assimilates -- it does recursive unwrapping (NOT
> "flattening") of the thenable. Assimilation aside, Q is an autolifter, so
> I'll can it an "assimilating autolifter". Assume a system is which this Q
> function and .then are the only lift-ish operations, and that .then is also
> an assimilating autolifter. What guarantee is supposed to follow?

.then() *cannot* be assimilating, nor can the default autolifter.
Assimilation needs to be something explicit and separate, or else
things will mysteriously break when you mix in objects like from
Casper.js.

It's not cool, nor is it necessary, for promises to forever poison the
landscape of "objects that happen to have a method named 'then'".

> Assuming that the thenable check is if(typeof x.then === 'function'),
> intuitively, we might think that
>
> Q(p).then(shouldntBeThenable => alert(typeof shouldntBeThenable.then));
>
> should never alert 'function'. But this guarantee does not follow:
>
> var p = {};
> Q(p).then(shouldntBeThenable => alert(typeof shouldntBeThenable.then));
> p.then = function() { return "gotcha"; };
>
> This is a consequence of assimilation being a dirty hack. The notion of a
> thenable is only marginally more principled than the notion of array-like.
> It is not a stable property. Above, p became a thenable after Q already
> judged it to be a non-promise non-thenable and hence returned a
> promise-for-p. This promise scheduled a call to the callback before p became
> a thenable, but p became a thenable before the callback got called. Alleged
> guarantee broken.
>
> Thus, no fundamental guarantee would be lost by introducing an expert-only
> operation that does autolifting but no unwrapping. This would make
> convenient what is possible anyway: promises-for-thenables. But this is
> *not* an argument for introducing a full lifting operation. Introducing that
> would break an important guarantee -- that there are no
> promises-for-promises. Without full lifting, promises-for-promises remain
> impossible.

Why do think that "no promises-for-promises" is an important guarantee?

> I leave it to monad fans and/or haters of assimilation to suggest names for
> this convenient operation, a non-unwrapping autolifter. I'm confident that
> if I tried to name it, I'd only cause more confusion ;).

It should just be called "resolve", as in Future.resolve().

> [1] FWIW, if there's interest, I can provide several examples where a
> promise-for-promise is useful, but I know of no compelling example. The
> utility of the examples I have in mind are not worth the cost of introducing
> this possibility of a promise-for-promise.

Given the demonstrated difficulty of accidentally creating a
promise-for-a-promise, why do you feel it is so important to guarantee
that it can't ever happen?

~TJ
Ron Buckton
2013-04-29 18:03:28 UTC
Permalink
Thanks for the clarifications re: Future as a monad. My understanding of this is as follows (please correct me if I am wrong):

* The expected result of the resolve/reject callbacks passed to Future#then is itself a Future.
* If the result of the resolve/reject callbacks is not a Future it is logically lifted into a Future, by accepting the value on the Future that is the result of Future#then.
* The Future result of the callback is merged into the Future returned Future#then then by chaining to either Future#then (Future#done?) of the callback result.
* Given this, it is not usually necessary to "recursively unwrap" or "flatten" a Future. As a result Future#then should not flatten the result. This would preserve Future.accept(Future.accept(value)).then().then(F => /* F is Future(value) */).

I can also agree that it is inherently unsafe to assimilate "thenables" for Future.resolve/FutureResolver#resolve, due to possible collisions with the property name on existing objects such as with casper.js. This kind of collision is the same rationale for having an @iterator symbol for iterators, though I wouldn't think the same resolution is likely the best result in this case. I am of the opinion that native Futures should only resolve native Futures (or possibly their subclasses). To assimilate you could have a Future.of (or possibly Future.from to match Array.from for "array-like" objects), which would assimilate "future-like" objects (i.e. "thenables"), but only via one level of unwrapping and not recursively.

I'm still concerned about cancellation, but will spin up another thread to hopefully spark some thoughtful discussion on the topic.

Ron

> -----Original Message-----
> From: Tab Atkins Jr. [mailto:jackalmage at gmail.com]
> Sent: Saturday, April 27, 2013 8:32 PM
> To: Mark Miller
> Cc: David Sheets; Mark S. Miller; es-discuss; public-script-coord at w3.org; Ron
> Buckton; David Bruant; Dean Tribble
> Subject: Re: A Challenge Problem for Promise Designers
>
> On Sat, Apr 27, 2013 at 5:46 PM, Mark Miller <erights at gmail.com> wrote:
> > I am worried that we're again separated by a common terminology more
> > than we are by an actual technical disagreement. I am arguing against
> > an unconditional lift operation that would make a promise-for-promise.
> > Or at least seeking to provoke someone to provide a compelling example
> > showing why this is useful[1]. What all the recent messages seem to be
> > arguing for is the existence of a non-assimilating and perhaps a
> > non-unwrapping lift-ish operation, so that one can make a
> > promise-for-thenable. I have no objection to being able to make a
> promise-for-thenable.
>
> The reasoning *for* an unconditional lift operator is that promises, as
> specified by DOM Futures or Promises/A+, are roughly a monad, and having
> an unconditional lift is required to satisfy the monad laws.
> Being a monad is useful, in ways similar to how being an iterable is useful, or
> an array-like, or many other typeclasses that various languages recognize.
>
> For example, Future.all() is almost equivalent to a variadic liftA() (name taken
> from Haskell), which takes a function and any number of arguments, all
> wrapped in some particular monad, and executes the function with its
> arguments in the way that the monad prefers. For promises, that means "if
> every promise accepts, execute the function and return an accepted promise
> for the return value; otherwise, return a rejected promise". This sort of
> function comes *for free* once a class has been established as a monad, just
> as Python's very useful itertools library applies "for free" to anything that
> establishes itself as an iterable. You don't have to write any special code to
> make it happen; just the fact that promises are monads means that the
> generically-written liftA() method automatically works.
>
> > The clearest sign to me of the potential for misunderstanding is the
> > use of the term "flattening" for unwrapping of thenables. To be clear,
> "flattening"
> > is about promises -- it is just the conditional autolifting seen from
> > the other side (as I now understand the term autolifting -- thanks).
> > "unwrapping" is what happens to thenables. "Assimilation" is recursive
> > unwrapping of thenables. I understand that it can be difficult to keep
> > these distinctions straight. I wouldn't be surprised if I've been
> > sloppy myself with these terms earlier in these threads. But now that
> > we're zeroing in, these fine distinctions matter.
>
> I'm not sure where you got this precise terminology, but I'm willing to adopt
> it for these discussions if it helps reduce confusion.
>
> > As I demonstrated somewhere in one of the Promises/A+ threads, I don't
> > think it is practical to prohibit these promises-for-thenables anyway.
> > As an example, let's take Q's use of Q as an allegedly fully
> > assimilating lift-ish function. Like an autolifter Q(promise) returns
> > that promise. And Q(x) where x is neither a promise nor a thenable,
> > returns promise-for-x. The controversial part -- which I fully
> > sympathize with since it is a dirty hack
> > -- is that Q(thenable) assimilates -- it does recursive unwrapping
> > (NOT
> > "flattening") of the thenable. Assimilation aside, Q is an autolifter,
> > so I'll can it an "assimilating autolifter". Assume a system is which
> > this Q function and .then are the only lift-ish operations, and that
> > .then is also an assimilating autolifter. What guarantee is supposed to
> follow?
>
> .then() *cannot* be assimilating, nor can the default autolifter.
> Assimilation needs to be something explicit and separate, or else things will
> mysteriously break when you mix in objects like from Casper.js.
>
> It's not cool, nor is it necessary, for promises to forever poison the landscape
> of "objects that happen to have a method named 'then'".
>
> > Assuming that the thenable check is if(typeof x.then === 'function'),
> > intuitively, we might think that
> >
> > Q(p).then(shouldntBeThenable => alert(typeof
> > shouldntBeThenable.then));
> >
> > should never alert 'function'. But this guarantee does not follow:
> >
> > var p = {};
> > Q(p).then(shouldntBeThenable => alert(typeof
> shouldntBeThenable.then));
> > p.then = function() { return "gotcha"; };
> >
> > This is a consequence of assimilation being a dirty hack. The notion
> > of a thenable is only marginally more principled than the notion of array-
> like.
> > It is not a stable property. Above, p became a thenable after Q
> > already judged it to be a non-promise non-thenable and hence returned
> > a promise-for-p. This promise scheduled a call to the callback before
> > p became a thenable, but p became a thenable before the callback got
> > called. Alleged guarantee broken.
> >
> > Thus, no fundamental guarantee would be lost by introducing an
> > expert-only operation that does autolifting but no unwrapping. This
> > would make convenient what is possible anyway: promises-for-thenables.
> > But this is
> > *not* an argument for introducing a full lifting operation.
> > Introducing that would break an important guarantee -- that there are
> > no promises-for-promises. Without full lifting, promises-for-promises
> > remain impossible.
>
> Why do think that "no promises-for-promises" is an important guarantee?
>
> > I leave it to monad fans and/or haters of assimilation to suggest
> > names for this convenient operation, a non-unwrapping autolifter. I'm
> > confident that if I tried to name it, I'd only cause more confusion ;).
>
> It should just be called "resolve", as in Future.resolve().
>
> > [1] FWIW, if there's interest, I can provide several examples where a
> > promise-for-promise is useful, but I know of no compelling example.
> > The utility of the examples I have in mind are not worth the cost of
> > introducing this possibility of a promise-for-promise.
>
> Given the demonstrated difficulty of accidentally creating a promise-for-a-
> promise, why do you feel it is so important to guarantee that it can't ever
> happen?
>
> ~TJ
Tab Atkins Jr.
2013-04-29 18:21:27 UTC
Permalink
On Mon, Apr 29, 2013 at 11:03 AM, Ron Buckton <rbuckton at chronicles.org> wrote:
> Thanks for the clarifications re: Future as a monad. My understanding of this is as follows (please correct me if I am wrong):
>
> * The expected result of the resolve/reject callbacks passed to Future#then is itself a Future.
> * If the result of the resolve/reject callbacks is not a Future it is logically lifted into a Future, by accepting the value on the Future that is the result of Future#then.
> * The Future result of the callback is merged into the Future returned Future#then then by chaining to either Future#then (Future#done?) of the callback result.
> * Given this, it is not usually necessary to "recursively unwrap" or "flatten" a Future. As a result Future#then should not flatten the result. This would preserve Future.accept(Future.accept(value)).then().then(F => /* F is Future(value) */).

All correct. (Some of your precise mechanical details are probably
wrong, but you got all the important bits.)

> I can also agree that it is inherently unsafe to assimilate "thenables" for Future.resolve/FutureResolver#resolve, due to possible collisions with the property name on existing objects such as with casper.js. This kind of collision is the same rationale for having an @iterator symbol for iterators, though I wouldn't think the same resolution is likely the best result in this case. I am of the opinion that native Futures should only resolve native Futures (or possibly their subclasses). To assimilate you could have a Future.of (or possibly Future.from to match Array.from for "array-like" objects), which would assimilate "future-like" objects (i.e. "thenables"), but only via one level of unwrapping and not recursively.

I'm fine with the concept of using branding (via a symbol) to denote
that something should be treated like a future. I'm also fine with
only allowing Promises and subclasses. Whatever people decide is
better.

Domenic, after a lot of experience, thinks that the assimilation
procedure should be recursive (presumably "bottoming out" when it hits
a native promise). I'm fine with that. It does mean that if you need
to assimilate a thenable for a Casper object, it'll do the wrong
thing, but that's the price you pay for arbitrary library compat. If
you know your foreign thenable is only a single layer, you can
safeguard its contents yourself, by chaining .then() and returning a
native Promise holding the value. Then, the assimilation procedure
will "eat" the thenable, hit the Promise and adopt its state, and the
Casper object (or whatever) will be safe.

> I'm still concerned about cancellation, but will spin up another thread to hopefully spark some thoughtful discussion on the topic.

Go for it. I have given some thought to it, and think I have a
reasonable model.

~TJ
Ron Buckton
2013-04-29 20:07:05 UTC
Permalink
> -----Original Message-----
> From: Tab Atkins Jr. [mailto:jackalmage at gmail.com]
> Sent: Monday, April 29, 2013 11:21 AM
> To: Ron Buckton
> Cc: Mark Miller; David Sheets; Mark S. Miller; es-discuss; public-script-
> coord at w3.org; David Bruant; Dean Tribble
> Subject: Re: A Challenge Problem for Promise Designers
>
> On Mon, Apr 29, 2013 at 11:03 AM, Ron Buckton <rbuckton at chronicles.org>
> wrote:
> > Thanks for the clarifications re: Future as a monad. My understanding of
> this is as follows (please correct me if I am wrong):
> >
> > * The expected result of the resolve/reject callbacks passed to
> Future#then is itself a Future.
> > * If the result of the resolve/reject callbacks is not a Future it is logically
> lifted into a Future, by accepting the value on the Future that is the result of
> Future#then.
> > * The Future result of the callback is merged into the Future returned
> Future#then then by chaining to either Future#then (Future#done?) of the
> callback result.
> > * Given this, it is not usually necessary to "recursively unwrap" or "flatten"
> a Future. As a result Future#then should not flatten the result. This would
> preserve Future.accept(Future.accept(value)).then().then(F => /* F is
> Future(value) */).
>
> All correct. (Some of your precise mechanical details are probably wrong, but
> you got all the important bits.)

I updated [1] my rough implementation of Future based on this discussion. This has the following changes from the previous [2] version which was based on the DOM spec for Future:

* The resolver's resolve algorithm tests value to determine if it is a Future instance (rather than a "thenable"). This could later be done by checking branding or by checking for a symbol.
* The resolver's resolve algorithm only unwraps the value once if it is a Future, rather than performing flattening. It does this by calling the resolver's accept algorithm in the "resolve" future callback for rather than the resolve algorithm.
* In the steps for Future#then, if the "resolveCallback" is null, the "resolve" callback becomes a future callback for resolver and its accept algorithm. This is to preserve the value for something like:

Future.accept(Future.accept(value)).then().then(F => /* F is Future(value) */)
Future.accept(Future.accept(Future.accept(value))).then().then(FF => /* FF is Future(Future(value)) */)

* In the steps for some/any/every, the future callbacks that are created that used the resolver's resolve algorithm now use the resolver's accept algorithm. This is to preserve the value for something like:

Future.any(Future.accept(Future.accept(value))).then(F => /* F is Future(value) */)
Future.any(Future.accept(Future.accept(Future.accept(value)))).then(FF => /* FF is Future(Future(value)) */)

* Added Future.from to perform explicit assimilation (with only one level of unwrap, as with Future#then)
* Added Future.isFuture to test for native Futures

Hopefully I've captured the mechanical details correctly in the implementation.

[1, updated implementation] https://github.com/rbuckton/promisejs/blob/master/Future1/Future.ts
[2, spec implementation] https://github.com/rbuckton/promisejs/blob/master/Future0/Future.ts

Ron

>
> > I can also agree that it is inherently unsafe to assimilate "thenables" for
> Future.resolve/FutureResolver#resolve, due to possible collisions with the
> property name on existing objects such as with casper.js. This kind of collision
> is the same rationale for having an @iterator symbol for iterators, though I
> wouldn't think the same resolution is likely the best result in this case. I am of
> the opinion that native Futures should only resolve native Futures (or
> possibly their subclasses). To assimilate you could have a Future.of (or
> possibly Future.from to match Array.from for "array-like" objects), which
> would assimilate "future-like" objects (i.e. "thenables"), but only via one
> level of unwrapping and not recursively.
>
> I'm fine with the concept of using branding (via a symbol) to denote that
> something should be treated like a future. I'm also fine with only allowing
> Promises and subclasses. Whatever people decide is better.
>
> Domenic, after a lot of experience, thinks that the assimilation procedure
> should be recursive (presumably "bottoming out" when it hits a native
> promise). I'm fine with that. It does mean that if you need to assimilate a
> thenable for a Casper object, it'll do the wrong thing, but that's the price you
> pay for arbitrary library compat. If you know your foreign thenable is only a
> single layer, you can safeguard its contents yourself, by chaining .then() and
> returning a native Promise holding the value. Then, the assimilation
> procedure will "eat" the thenable, hit the Promise and adopt its state, and
> the Casper object (or whatever) will be safe.
>
> > I'm still concerned about cancellation, but will spin up another thread to
> hopefully spark some thoughtful discussion on the topic.
>
> Go for it. I have given some thought to it, and think I have a reasonable
> model.
>
> ~TJ
Tab Atkins Jr.
2013-04-29 20:20:14 UTC
Permalink
On Mon, Apr 29, 2013 at 1:07 PM, Ron Buckton <rbuckton at chronicles.org> wrote:
> I updated [1] my rough implementation of Future based on this discussion. This has the following changes from the previous [2] version which was based on the DOM spec for Future:
>
> * The resolver's resolve algorithm tests value to determine if it is a Future instance (rather than a "thenable"). This could later be done by checking branding or by checking for a symbol.
> * The resolver's resolve algorithm only unwraps the value once if it is a Future, rather than performing flattening. It does this by calling the resolver's accept algorithm in the "resolve" future callback for rather than the resolve algorithm.
> * In the steps for Future#then, if the "resolveCallback" is null, the "resolve" callback becomes a future callback for resolver and its accept algorithm. This is to preserve the value for something like:
>
> Future.accept(Future.accept(value)).then().then(F => /* F is Future(value) */)
> Future.accept(Future.accept(Future.accept(value))).then().then(FF => /* FF is Future(Future(value)) */)
>
> * In the steps for some/any/every, the future callbacks that are created that used the resolver's resolve algorithm now use the resolver's accept algorithm. This is to preserve the value for something like:
>
> Future.any(Future.accept(Future.accept(value))).then(F => /* F is Future(value) */)
> Future.any(Future.accept(Future.accept(Future.accept(value)))).then(FF => /* FF is Future(Future(value)) */)

This all sounds right.

> * Added Future.from to perform explicit assimilation (with only one level of unwrap, as with Future#then)

Like I said, Domenic says that recursive assimilation is useful, and
I'm inclined to believe him, as he has a lot more experience in
getting arbitrary thenables to play nicely together than I do. ^_^

> * Added Future.isFuture to test for native Futures

For the purpose of library code, you don't need this - just use "x
instanceof Future". Future.isFuture is only useful for the language
to define, so that it can tell something is a Future cross-frame.

~TJ
Ron Buckton
2013-04-29 20:39:17 UTC
Permalink
> -----Original Message-----
> From: Tab Atkins Jr. [mailto:jackalmage at gmail.com]
> Sent: Monday, April 29, 2013 1:20 PM
> To: Ron Buckton
> Cc: Mark Miller; David Sheets; Mark S. Miller; es-discuss; public-script-
> coord at w3.org; David Bruant; Dean Tribble
> Subject: Re: A Challenge Problem for Promise Designers
>


> > * Added Future.from to perform explicit assimilation (with only one
> > level of unwrap, as with Future#then)
>
> Like I said, Domenic says that recursive assimilation is useful, and I'm inclined
> to believe him, as he has a lot more experience in getting arbitrary thenables
> to play nicely together than I do. ^_^

I'll tinker with it and run some tests.

> > * Added Future.isFuture to test for native Futures
>
> For the purpose of library code, you don't need this - just use "x instanceof
> Future". Future.isFuture is only useful for the language to define, so that it
> can tell something is a Future cross-frame.

The intent is to eventually have a rough polyfill for ES5 and earlier, so if Future.isFuture becomes part of the spec this would likely match using some kind of pseudo-symbol polyfill.

>
> ~TJ

Ron
Juan Ignacio Dopazo
2013-04-26 13:43:46 UTC
Permalink
2013/4/26 Kevin Smith <zenparsing at gmail.com>

> What exactly is the controversy here?
>
> I think we all agree with the semantics of "then" as specified in
> Promises/A+. (If not, then we have a really big problem!)
>

Promise/A+ does not prohibit promises for promises. But in practice the
problem is recognizing what is a promise. There are two options:

1) Recognize all thenables as promises
2) Recognize only the promises from your own library

Many implementations go with (1), including ODMFuture. Since it doesn't
distinguish between promises and thenables, then() flattens all of them.

Juan
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130426/0a5d2912/attachment-0001.html>
David Sheets
2013-04-26 14:20:16 UTC
Permalink
> 2013/4/26 Kevin Smith <zenparsing at gmail.com>
>>
>> What exactly is the controversy here?

I believe the controversy is over the number of resolution iterations
for a given wrapped value.

>> I think we all agree with the semantics of "then" as specified in
>> Promises/A+. (If not, then we have a really big problem!)

I believe the semantics of "then" refer to the semantics of
[[Resolve]] in Promises/A+. The controversy, as far as I can tell, is
over whether [[Resolve]].2.3.1 at
<https://github.com/promises-aplus/promises-spec> should read:

If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).

OR

If/when resolvePromise is called with a value y, fulfill promise with y.

On Fri, Apr 26, 2013 at 2:43 PM, Juan Ignacio Dopazo
<dopazo.juan at gmail.com> wrote:
> Promise/A+ does not prohibit promises for promises. But in practice the
> problem is recognizing what is a promise. There are two options:
>
> 1) Recognize all thenables as promises
> 2) Recognize only the promises from your own library
>
> Many implementations go with (1), including ODMFuture. Since it doesn't
> distinguish between promises and thenables, then() flattens all of them.

This does not appear to be the behavior specified in Promises/A+ as
far as I can tell.

If onFulfilled is called with a "promise", that value is returned. If
onFulfilled is called with a normal value, that value is lifted into a
promise and returned. If onFulfilled is called with a non-promise
"thenable", the "thenable" is chained *and then re-resolved* per
2.3.1.

It is this recursive re-resolution which is up for debate.

PRO:

Re-resolution prevents confusing nesting

CON:

Re-resolution causes confusing recursive behavior

In the common case (no nesting), both above versions of 2.3.1 behave
identically. In the exceptional case, the non-recursive version is
easier to reason about because the resolution doesn't strip away all
your thenables.

>From my reading, DOM Futures doesn't state anything about resolution
semantics, to its detriment, but abstracts those semantics behind
FutureResolver.

Have I presented this correctly? Why is it a problem to specify a
single level of assimilation instead of sometimes-flattening
"thenables" but never flattening promises?

Have I missed something?

Thanks,

David
David Sheets
2013-04-26 14:24:23 UTC
Permalink
On Fri, Apr 26, 2013 at 3:20 PM, David Sheets <kosmo.zb at gmail.com> wrote:
>> 2013/4/26 Kevin Smith <zenparsing at gmail.com>
>>>
>>> What exactly is the controversy here?
>
> I believe the controversy is over the number of resolution iterations
> for a given wrapped value.
>
>>> I think we all agree with the semantics of "then" as specified in
>>> Promises/A+. (If not, then we have a really big problem!)
>
> I believe the semantics of "then" refer to the semantics of
> [[Resolve]] in Promises/A+. The controversy, as far as I can tell, is
> over whether [[Resolve]].2.3.1 at
> <https://github.com/promises-aplus/promises-spec> should read:
>
> If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).
>
> OR
>
> If/when resolvePromise is called with a value y, fulfill promise with y.
>
> On Fri, Apr 26, 2013 at 2:43 PM, Juan Ignacio Dopazo
> <dopazo.juan at gmail.com> wrote:
>> Promise/A+ does not prohibit promises for promises. But in practice the
>> problem is recognizing what is a promise. There are two options:
>>
>> 1) Recognize all thenables as promises
>> 2) Recognize only the promises from your own library
>>
>> Many implementations go with (1), including ODMFuture. Since it doesn't
>> distinguish between promises and thenables, then() flattens all of them.
>
> This does not appear to be the behavior specified in Promises/A+ as
> far as I can tell.
>
> If onFulfilled is called with a "promise", that value is returned. If
> onFulfilled is called with a normal value, that value is lifted into a
> promise and returned. If onFulfilled is called with a non-promise
> "thenable", the "thenable" is chained *and then re-resolved* per
> 2.3.1.

Gah! This, of course, should read:

If onFulfilled *returns* a "promise", that value is returned. If
onFulfilled *returns* a normal value, that value is lifted into a
promise and returned. If onFulfilled *returns* a non-promise
"thenable", the "thenable" is chained and then re-resolved per
2.3.1.

> It is this recursive re-resolution which is up for debate.
>
> PRO:
>
> Re-resolution prevents confusing nesting
>
> CON:
>
> Re-resolution causes confusing recursive behavior
>
> In the common case (no nesting), both above versions of 2.3.1 behave
> identically. In the exceptional case, the non-recursive version is
> easier to reason about because the resolution doesn't strip away all
> your thenables.
>
> From my reading, DOM Futures doesn't state anything about resolution
> semantics, to its detriment, but abstracts those semantics behind
> FutureResolver.
>
> Have I presented this correctly? Why is it a problem to specify a
> single level of assimilation instead of sometimes-flattening
> "thenables" but never flattening promises?
>
> Have I missed something?
>
> Thanks,
>
> David
Domenic Denicola
2013-04-26 14:27:22 UTC
Permalink
From: David Sheets [kosmo.zb at gmail.com]

> From my reading, DOM Futures doesn't state anything about resolution semantics, to its detriment, but abstracts those semantics behind `FutureResolver`.

This is not correct. See "Let resolve be a future callback for the context object and its resolve algorithm." inside the resolve algorithm itself. DOM Futures are recursive, just like Promises/A+.

> Have I presented this correctly?

Yes.

> Why is it a problem to specify a single level of assimilation instead of sometimes-flattening "thenables" but never flattening promises?

The idea is that conformant libraries may want to prohibit promises-for-thenables (since, as discussed many times already, they are nonsensical, unless you care more about monads than you do about promises---which all promise libraries I know of do not). To do so, two things must occur:

1. The library must never allow creation of promises-for-thenables. That is, it must not provide a `fulfilledPromise(value)` method, only a `resolvedPromise(value)` method. DOM Future violates this by providing `accept(value)`, but presumably TC39-sanctioned promises will not provide such a method.

2. The library must prevent thenables-for-thenables from turning into promises-for-thenables via assimilation. Instead, it must do recursive unwrapping.

In this way, Promises/A+ allows promises-for-promises within a library, if that library allows creation of such things in the first place (like DOM Future does). But it does not allow promises-for-thenables, i.e. it does not allow foreign promises-for-promises to infect a library with multiple layers of wrapping. Multi-layered wrapping *must* stay within a single library.


(In the above I am using "thenable" in the sense it is used today, i.e. "object with a then method," not Claus's version.)
David Sheets
2013-04-26 14:52:54 UTC
Permalink
On Fri, Apr 26, 2013 at 3:27 PM, Domenic Denicola
<domenic at domenicdenicola.com> wrote:
> From: David Sheets [kosmo.zb at gmail.com]
>
>> From my reading, DOM Futures doesn't state anything about resolution semantics, to its detriment, but abstracts those semantics behind `FutureResolver`.
>
> This is not correct. See "Let resolve be a future callback for the context object and its resolve algorithm." inside the resolve algorithm itself. DOM Futures are recursive, just like Promises/A+.

Ah, you are correct and this would appear to unnecessarily break
expected identities. Though, it's at least consistent instead of
special casing its own promises.

>> Have I presented this correctly?
>
> Yes.
>
>> Why is it a problem to specify a single level of assimilation instead of sometimes-flattening "thenables" but never flattening promises?
>
> The idea is that conformant libraries may want to prohibit promises-for-thenables (since, as discussed many times already, they are nonsensical, unless you care more about monads than you do about promises---which all promise libraries I know of do not). To do so, two things must occur:
>
> 1. The library must never allow creation of promises-for-thenables. That is, it must not provide a `fulfilledPromise(value)` method, only a `resolvedPromise(value)` method. DOM Future violates this by providing `accept(value)`, but presumably TC39-sanctioned promises will not provide such a method.
>
> 2. The library must prevent thenables-for-thenables from turning into promises-for-thenables via assimilation. Instead, it must do recursive unwrapping.
>
> In this way, Promises/A+ allows promises-for-promises within a library, if that library allows creation of such things in the first place (like DOM Future does). But it does not allow promises-for-thenables, i.e. it does not allow foreign promises-for-promises to infect a library with multiple layers of wrapping. Multi-layered wrapping *must* stay within a single library.

Why is there a semantic distinction between my thenables and your thenables?

If someone is using nested thenables, presumably they have a good
reason. Promises/A+ acknowledges this possibility by allowing
own-promises to nest. If we are interesting in constructing the "most
standard" promises system, surely this system must grant other,
foreign systems the same possibility of nesting own-promises without
interference? Of course, these systems also control their own
resolution semantics and *could opt* to magically flatten all results.

Could you point me to some code that needs dynamic flattening?

I understand the need for automatic lifting and 1-level assimilation.
I use these a lot. I'm still fuzzy on the utility of flattening k
levels for dynamic k.

Thanks,

David
Domenic Denicola
2013-04-26 15:03:56 UTC
Permalink
From: David Sheets [kosmo.zb at gmail.com]

> Why is there a semantic distinction between my thenables and your thenables?

Because your thenables are not to be trusted! They could do pathological things like jQuery, or conceptually incoherent things like thenables-for-thenables. Sanitation at the boundary is the idea behind the resolve algorithm.

> If someone is using nested thenables, presumably they have a good reason. Promises/A+ acknowledges this possibility by allowing own-promises to nest.

Yes, but more importantly, it preserves the guarantees within a single library---whether they be allowing promises-for-thenables, or disallowing them. Q, when, RSVP, and others guarantee no promises-for-thenables. That is a great feature for consumers of those libraries, as has been emphasized many times in this thread (especially elegantly, I think, by David Bruant). If there were no recursive foreign thenable assimilation, then promises-for-thenables could sneak into Q/when/RSVP promise systems, breaking the assumptions of consumers of those promises.

> If we are interesting in constructing the "most standard" promises system, surely this system must grant other, foreign systems the same possibility of nesting own-promises without interference?

No. Generally, foreign systems *must* be normalized, for security concerns if nothing else. Trying to accommodate foreign system semantics into your own promise system is a recipe for disaster. Mark can expand upon this more in detail, if you think it's an important point.

> Could you point me to some code that needs dynamic flattening?

>From https://github.com/promises-aplus/promises-spec/issues/101#issuecomment-16657518

> ```js
> var promise = 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 `[[Resolve]]`, this is a promise for undefined that will be rejected with the appropriate error if any of the operations failed. But if it did one-level unwrapping, this would be a QPFAUDPFAQPFU, and it would always fulfill---failure information would have to be manually extracted.

Hope that helps!
David Sheets
2013-04-26 15:45:38 UTC
Permalink
On Fri, Apr 26, 2013 at 4:03 PM, Domenic Denicola
<domenic at domenicdenicola.com> wrote:
> From: David Sheets [kosmo.zb at gmail.com]
>
>> Why is there a semantic distinction between my thenables and your thenables?
>
> Because your thenables are not to be trusted! They could do pathological things like jQuery, or conceptually incoherent things like thenables-for-thenables. Sanitation at the boundary is the idea behind the resolve algorithm.

But I am the programmer! If my own tools distrust me, all hope is
lost. With the present special casing of own-promises, there can only
ever be a single semantics (own-promises) even if I have an
alternative semantics that I would like to use.

>> If someone is using nested thenables, presumably they have a good reason. Promises/A+ acknowledges this possibility by allowing own-promises to nest.
>
> Yes, but more importantly, it preserves the guarantees within a single library---whether they be allowing promises-for-thenables, or disallowing them.

It destroys the guarantee that foreign libraries are in charge of
their own resolution. Preserving foreign libraries' guarantees would
require meddling with their values the *least* (1-level assimilation).

> Q, when, RSVP, and others guarantee no promises-for-thenables. That is a great feature for consumers of those libraries, as has been emphasized many times in this thread (especially elegantly, I think, by David Bruant).

That's a great feature for those programmers! If Q, when, and RSVP
make that guarantee, they should enforce it by recursively resolving
in *their* bind (or assimilation) operation, not relying on
Promises/A+ or DOM Future to enforce it for them.

> If there were no recursive foreign thenable assimilation, then promises-for-thenables could sneak into Q/when/RSVP promise systems, breaking the assumptions of consumers of those promises.

After their recursively resolving bind? How?

If they have a thenable and use their libraries' bind, their invariant
is maintained.

>> If we are interesting in constructing the "most standard" promises system, surely this system must grant other, foreign systems the same possibility of nesting own-promises without interference?
>
> No. Generally, foreign systems *must* be normalized, for security concerns if nothing else.

What, specifically, are these security concerns?

> Trying to accommodate foreign system semantics into your own promise system is a recipe for disaster. Mark can expand upon this more in detail, if you think it's an important point.

It depends on what invariants are maintained, I believe. I've read
quite a number of threads about this topic (including far too many
pages of GitHub issues) and I've not, yet, come across something that
indicates that delegating resolution to each own implementation breaks
things.

>> Could you point me to some code that needs dynamic flattening?
>
> From https://github.com/promises-aplus/promises-spec/issues/101#issuecomment-16657518
>
>> ```js
>> var promise = getDataFromServerUsingQ().then(function (data) {
>> return $('.foo').animate('opacity', data.opacityLevel).promise().then(function () {
>> return updateBackboneModelViaSomeThirdPartyLibraryUsingUnderscoreDeferred().then(function () {
>> return tellServerThatTheUpdateSucceededUsingQ();
>> });
>> });
>> });
>> ```

This looks like a case for static resolution to me. Like this:

```js
var promise = getDataFromServerUsingQ().then(function (data) {
return Q($('.foo').animate('opacity',
data.opacityLevel).promise()).then(function () {
return Q(updateBackboneModelViaSomeThirdPartyLibraryUsingUnderscoreDeferred()).then(function
() {
return tellServerThatTheUpdateSucceededUsingQ();
});
});
});
```

I favor this expression because it *explicitly* invokes Q's behavior
early and every use of 'then' is Q's 'then'. It costs 6 more bytes. Do
you have any examples where you *don't know* until runtime how many
thenables are wrapped?

>> If Q, as a proper Promises/A+ library, does recursive `[[Resolve]]`, this is a promise for undefined that will be rejected with the appropriate error if any of the operations failed. But if it did one-level unwrapping, this would be a QPFAUDPFAQPFU, and it would always fulfill---failure information would have to be manually extracted.

If each of the wrapped promises also did one-level unwrapping, Q would
not have to do dynamic unwrapping. If you use Q's assimilating
constructor, you don't need dynamic unwrapping. If one-level
unwrapping were the standard, all these libraries could interoperate
*without* recursive resolution.

If we're looking for the most flexible, straightforward, easiest, and
future-proofed semantics, it seems to me that decreasing the amount of
magic inside the abstraction is nearly always better.

Does that make sense?

David
Tab Atkins Jr.
2013-04-26 17:00:34 UTC
Permalink
On Fri, Apr 26, 2013 at 8:45 AM, David Sheets <kosmo.zb at gmail.com> wrote:
>>> Could you point me to some code that needs dynamic flattening?
>>
>> From https://github.com/promises-aplus/promises-spec/issues/101#issuecomment-16657518
>>
>>> ```js
>>> var promise = getDataFromServerUsingQ().then(function (data) {
>>> return $('.foo').animate('opacity', data.opacityLevel).promise().then(function () {
>>> return updateBackboneModelViaSomeThirdPartyLibraryUsingUnderscoreDeferred().then(function () {
>>> return tellServerThatTheUpdateSucceededUsingQ();
>>> });
>>> });
>>> });
>>> ```
>
> This looks like a case for static resolution to me. Like this:
>
> ```js
> var promise = getDataFromServerUsingQ().then(function (data) {
> return Q($('.foo').animate('opacity',
> data.opacityLevel).promise()).then(function () {
> return Q(updateBackboneModelViaSomeThirdPartyLibraryUsingUnderscoreDeferred()).then(function
> () {
> return tellServerThatTheUpdateSucceededUsingQ();
> });
> });
> });
> ```
>
> I favor this expression because it *explicitly* invokes Q's behavior
> early and every use of 'then' is Q's 'then'. It costs 6 more bytes. Do
> you have any examples where you *don't know* until runtime how many
> thenables are wrapped?

Agreed. This is just an example of the
libraries-with-mutually-unrecognizable-promises problem, which is a
very specific issue with only a weak connection to the wider
recursive-resolve problem. As I've argued before, and David is
arguing here, the correct response to this problem is to have an
explicit assimilation operation for converting foreign promises into
your preferred version, and just sprinkling that around as necessary
when crossing library boundaries.

The need for this will decrease now that DOM Futures exist, and
libraries switch to using those (or a subclass of them) rather than
rolling bespoke promises.

~TJ
Domenic Denicola
2013-04-26 17:03:24 UTC
Permalink
From: Tab Atkins Jr. [jackalmage at gmail.com]

> The need for this will decrease now that DOM Futures exist, and libraries switch to using those (or a subclass of them) rather than rolling bespoke promises.

Last I heard, jQuery has committed to never switching their promises implementation to one that works, for backward compatibility reasons. Rick might know more about if thinking has changed recently, though.

Even then, it's very naive to expect all code will be upgraded to subclass a DOM API.
Tab Atkins Jr.
2013-04-26 18:01:46 UTC
Permalink
On Fri, Apr 26, 2013 at 10:03 AM, Domenic Denicola
<domenic at domenicdenicola.com> wrote:
> From: Tab Atkins Jr. [jackalmage at gmail.com]
>> The need for this will decrease now that DOM Futures exist, and libraries switch to using those (or a subclass of them) rather than rolling bespoke promises.
>
> Last I heard, jQuery has committed to never switching their promises implementation to one that works, for backward compatibility reasons. Rick might know more about if thinking has changed recently, though.

Yeah, that's fine. jQuery's promises are super-broken anyway, so it
was unlikely they'd be able to change over to being compliant with the
rest of the ecosystem. That's why we have explicit assimilation
functions (or, in jQuery's case, I expect them to provide a function
that converts their promise into a Future).

> Even then, it's very naive to expect all code will be upgraded to subclass a DOM API.

I hear some disdain in your text here. Why, because it's a "DOM API"?
Futures are only DOM because we needed it faster than TC39 could have
provided it, but the intention is that they'll migrate into the ES
spec in ES7 or so.

I *do* expect that a lot of smaller libraries which currently use
bespoke promises to upgrade into Futures or Future subclasses, simple
because they have less back-compat pressure and being auto-compatible
with the standard version is useful. I also expect that new libraries
will just use Futures or Future subclasses. (All of this will happen
*after* browsers actually implement Futures, of course.)

~TJ
Rick Waldron
2013-04-26 18:36:46 UTC
Permalink
On Apr 26, 2013 1:03 PM, "Domenic Denicola" <domenic at domenicdenicola.com>
wrote:
>
> From: Tab Atkins Jr. [jackalmage at gmail.com]
>
> > The need for this will decrease now that DOM Futures exist, and
libraries switch to using those (or a subclass of them) rather than rolling
bespoke promises.
>
> Last I heard, jQuery has committed to never switching their promises
implementation to one that works, for backward compatibility reasons. Rick
might know more about if thinking has changed recently, though.
>

Before I respond, let me make it clear that I have no intention of arguing
with anyone who chooses to follow up my comments. I don't agree with every
decision made by every committer to jQuery, and I'm not going to defend
decisions that I disagree with...

The libraries discussed in this and similar threads have the benefit of
very limited adoption, where breaking changes incur minimal costs. jQuery
doesn't have that luxury ;) [0] and therefore won't break backward
compatibility. I can assure you that we won't press for adoption of our
implementation as a standard?despite its more than adequate qualification
as a de facto standard[1] (like it or not).

Rick

[0] http://trends.builtwith.com/javascript/jQuery
[1] http://en.wikipedia.org/wiki/De_facto_standard
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130426/cc67a548/attachment.html>
David Bruant
2013-04-26 19:33:13 UTC
Permalink
Le 26/04/2013 20:36, Rick Waldron a ?crit :
> The libraries discussed in this and similar threads have the benefit
> of very limited adoption, where breaking changes incur minimal costs.
> jQuery doesn't have that luxury ;) [0] and therefore won't break
> backward compatibility. I can assure you that we won't press for
> adoption of our implementation as a standard?despite its more than
> adequate qualification as a de facto standard[1] (like it or not).
Which naturally leads to the question: why should platform promises be
compatible with Promise/A+ and not jQuery "promises"? Because more
libraries use Promise/A+? what about market share?

David
Domenic Denicola
2013-04-26 19:47:34 UTC
Permalink
From: David Bruant [bruant.d at gmail.com]

> Which naturally leads to the question: why should platform promises be compatible with Promise/A+ and not jQuery "promises"? Because more libraries use Promise/A+? what about market share?

What we're discussing is not *compatibility* but *ability to assimilate*. Assimilating thenables requires no particular spec compliance or library compatibility. Promises/A+ (in the 1.1 version) gives a step-by-step procedure for doing so that is quite resilient in the face of edge cases, and so I'd recommend it for any assimilation semantics, but that's not terribly relevant to the question of whether there should be assimilation semantics *at all*.
Dean Landolt
2013-04-26 20:07:32 UTC
Permalink
On Fri, Apr 26, 2013 at 3:47 PM, Domenic Denicola <
domenic at domenicdenicola.com> wrote:

> From: David Bruant [bruant.d at gmail.com]
>
> > Which naturally leads to the question: why should platform promises be
> compatible with Promise/A+ and not jQuery "promises"? Because more
> libraries use Promise/A+? what about market share?
>
> What we're discussing is not *compatibility* but *ability to assimilate*.


The ability to assimilate stems directly from a need for library
compatibility. Seriously -- ask Kris Kowal who twisted his arm into having
Q accept thenables? :P


> Assimilating thenables requires no particular spec compliance or library
> compatibility. Promises/A+ (in the 1.1 version) gives a step-by-step
> procedure for doing so that is quite resilient in the face of edge cases,
> and so I'd recommend it for any assimilation semantics, but that's not
> terribly relevant to the question of whether there should be assimilation
> semantics *at all*.
>

What I'd really like to know is what is supposed to happen when a casper.js
[1] instance is returned by a promise? There is a lot of this code in the
wild. It's one thing when we're just talking about libraries which users
intentionally choose. But baking these assimilation semantics into the
language will create subtle interactions that are non-trivial to find and
debug. And for what? Compatibility with existing Promises/A+ libraries that
could easily make themselves compatible in other ways? That hardly seems
worth it.

[1] http://casperjs.org/
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130426/bae8d9c9/attachment-0001.html>
David Bruant
2013-04-26 20:33:35 UTC
Permalink
Le 26/04/2013 21:47, Domenic Denicola a ?crit :
> From: David Bruant [bruant.d at gmail.com]
>> Which naturally leads to the question: why should platform promises be compatible with Promise/A+ and not jQuery "promises"? Because more libraries use Promise/A+? what about market share?

I realize I was playing the douchebag devil advocate here and I apologize for that.

The point I was trying to make is that there is no reason for the platform to follow a standard more than another. PromiseA+ has some user base and benefits, but jQuery has a much more massive user base and it would make sense for the platform to follow that. There are probably a bunch of other de facto standard here and there. Why should platform promises be more interoperable with promiseA+ and not any other (de facto) standard?

> What we're discussing is not *compatibility* but *ability to assimilate*.
I confused both as "having some level of interoperability".

> Assimilating thenables requires no particular spec compliance or library compatibility. Promises/A+ (in the 1.1 version) gives a step-by-step procedure for doing so that is quite resilient in the face of edge cases, and so I'd recommend it for any assimilation semantics, but that's not terribly relevant to the question of whether there should be assimilation semantics *at all*.
As is, the algorithm is unacceptable as it unconditionally calls the
then method of a thenable potentially resulting in unintended
side-effects if the thenable wasn't intended to be a promise but a
non-promise value.
I believe "does this object has a 'then' method?" is too weak of a
heuristic for a web platform convention. Trying an idea:
Step 2.3 of the Promise Resolution Procedure, instead of just "if then
is a function", what about "if then is a function *and* has a property
named '__IAmAPromiseA+Thenable__' which value is true, call it... "?

I don't think it would be possible (you'll tell me soon enough I
imagine), but that would be better.

Although this sounds crazier, it's a much better way to express the
intent that such then function is a thenable and I wouldn't be opposed
to this test even baked in the platform as it dramatically reduces the
risk of confusion.

David
Kevin Smith
2013-04-26 14:41:07 UTC
Permalink
>
>
> Promise/A+ does not prohibit promises for promises. But in practice the
> problem is recognizing what is a promise.
>

I would say rather that we have two orthogonal, but
highly interfering issues:

1. Do we allow promises-(for-promises)+?
2. How do we recognize a promise type within the "resolve" operation?

I was going to wait for confirmation on Q, but I'll go ahead and say
somethin' foolish anyway : )

## Proposal ##

1. I don't see a problem with allowing nested promises, although I do see
it as an anti-pattern which ought to be avoided if at all possible. I also
see promise-subclassing as an anti-pattern for the same reasons.

2. We test for promise-ness using an ES6 symbol, if available, and
otherwise the string "then".

- Let `thenName` be "then"
- If the Symbol function is provided, then let `thenName` be Symbol()

Within the `resolve` operation, replace step 3 with:

- If value is a JavaScript Object, set `then` to the result of calling the
JavaScript [[Get]] internal method of value with property name `thenName`.

Add a Future.thenName property which returns `thenName`.

And finally, in the Future constructor, add something to this effect:

- If `thenName` is not the string "then", then set future[thenName] =
future.then

It's a little hacky but I'm sure it could be cleaned up by you W3C chaps.
: )

Userland promise libraries would perform essentially the same operation in
their initialization routines:

if (typeof Future === "function" && typeof Future.thenName === "symbol")
this[Future.thenName] = this.then;

{ Kevin }
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130426/1ca73eda/attachment.html>
Tom Van Cutsem
2013-05-20 12:15:59 UTC
Permalink
(catching up on old threads, sorry for the asynchrony [no puns intended
;-)])

On 26 April 2013 12:19, David Bruant <bruant.d at gmail.com> wrote:
> > I have read somewhere (I can't remember where, hopefully MarkM will
> confirm
> > or say if I imagined it) that in E, if a variable contains a promise and
> > this promise is resolved, then the variable unwraps its value and
> > referencing to the variable suddenly means referencing to the value.
> > Promises are so deeply embedded in the language (syntax included) that
> this
> > unwrapping is seamless.
>

I can confirm that this is correct: E promises, once fulfilled, are
indistinguishable from their fulfilled value.

2013/4/26 Andreas Rossberg <rossberg at google.com>

> I'm not sure if your description of E is accurate -- I'd find that
> surprising. It _is_ a perfectly sensible design to have transparent
> futures that you can just use in place of the value they eventually
> get resolved to (let's call that value their 'content'). In fact, that
> is how futures/promises where originally proposed (back in the 70s)
> and implemented (e.g. in MultiLisp in 85, Oz in 95, and others later).
> However, there are really only two consistent points in the design
> space:
>
> 1. Either, futures are objects in their own right, with methods and
> everything. Then they should be fully compositional, and never be
> equated with their content, even after resolved (because that would
> change the meaning of a reference at locally unpredictable points in
> time).
>
> 2. Or, futures are entirely transparent, i.e. can be used everywhere
> as a placeholder for their content. In particular, that means that
> 'f.then' always denotes the 'then' method of the content, not of the
> future itself (even when the content value is not yet available).
>
> Unfortunately, option (2) requires that accessing a future that is not
> yet resolved has to (implicitly) block until it is (which is what
> happens in MultiLisp and friends). That only makes sense with threads,
> so I don't see how it can be reconciled with JavaScript.
>

Your conclusion is based on the premise that the operation used to
synchronize on the promise is expressed as a method.

E reconciles entirely transparent promises with non-blocking semantics by
introducing a language construct, called "when", to await the value of a
promise:

when ( promise ) -> {
// code here executed in later turn, when promise is fulfilled
// within this block of code, the promise *is* its fulfilled value
}

Semantically, the "when" operation acts on the promise itself, not on its
fulfilled value.
This distinction is blurred in JavaScript as "when" is expressed as a
".then" method on an explicit promise object.

Cheers,
Tom
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130520/e100bcad/attachment.html>
Andreas Rossberg
2013-05-21 09:39:25 UTC
Permalink
On 20 May 2013 14:15, Tom Van Cutsem <tomvc.be at gmail.com> wrote:
> 2013/4/26 Andreas Rossberg <rossberg at google.com>
>>
>> I'm not sure if your description of E is accurate -- I'd find that
>> surprising. It _is_ a perfectly sensible design to have transparent
>> futures that you can just use in place of the value they eventually
>> get resolved to (let's call that value their 'content'). In fact, that
>> is how futures/promises where originally proposed (back in the 70s)
>> and implemented (e.g. in MultiLisp in 85, Oz in 95, and others later).
>> However, there are really only two consistent points in the design
>> space:
>>
>> 1. Either, futures are objects in their own right, with methods and
>> everything. Then they should be fully compositional, and never be
>> equated with their content, even after resolved (because that would
>> change the meaning of a reference at locally unpredictable points in
>> time).
>>
>> 2. Or, futures are entirely transparent, i.e. can be used everywhere
>> as a placeholder for their content. In particular, that means that
>> 'f.then' always denotes the 'then' method of the content, not of the
>> future itself (even when the content value is not yet available).
>>
>> Unfortunately, option (2) requires that accessing a future that is not
>> yet resolved has to (implicitly) block until it is (which is what
>> happens in MultiLisp and friends). That only makes sense with threads,
>> so I don't see how it can be reconciled with JavaScript.
>
> Your conclusion is based on the premise that the operation used to
> synchronize on the promise is expressed as a method.
>
> E reconciles entirely transparent promises with non-blocking semantics by
> introducing a language construct, called "when", to await the value of a
> promise:
>
> when ( promise ) -> {
> // code here executed in later turn, when promise is fulfilled
> // within this block of code, the promise *is* its fulfilled value
> }

What's your definition of "entirely transparent" then? Or in other
words, what if I use 'promise' outside a when?

/Andreas
Tom Van Cutsem
2013-05-22 10:05:54 UTC
Permalink
2013/5/21 Andreas Rossberg <rossberg at google.com>

> What's your definition of "entirely transparent" then? Or in other
> words, what if I use 'promise' outside a when?
>

I clarified this with Andreas in person, but FTR: "entirely transparent" is
indeed the wrong word to describe E promises.

For context, E has two message passing operators, obj.m() indicates an
immediate call (as in JS), obj<-m() indicates an eventual send, aka
asynchronous message send, which returns a promise (this is the obj ! m()
syntax proposed in <
http://wiki.ecmascript.org/doku.php?id=strawman:concurrency>).

Outside of "when"-blocks, promises are only transparent w.r.t. "<-", not
w.r.t. "."

IOW: obj.m() will fail if obj is a promise, while obj<-m() will work
"transparently", regardless of whether obj is a promise or non-promise.

I hope this clarifies things.

Cheers,
Tom
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130522/252c2ca7/attachment.html>
Tab Atkins Jr.
2013-04-26 15:25:40 UTC
Permalink
On Fri, Apr 26, 2013 at 3:19 AM, David Bruant <bruant.d at gmail.com> wrote:
> Le ven. 26 avril 2013 11:43:35 CEST, Andreas Rossberg a ?crit :
>> On 26 April 2013 10:54, David Bruant <bruant.d at gmail.com> wrote:
>>> The Priority of Constituencies [1] asks us to be remain careful about
>>> theoretical standpoints. How does the theoretical part translates into
>>> helping users? authors (more than what I described at [2] which is
>>> derived
>>> from my own experience)? implementors? specifiers?
>>> I'm not saying the theoretical benefits don't exist, but I'd like to see
>>> how
>>> they translate in concretely improving my life as a developer using
>>> promises. I've explained the benefits I see for flattening from the dev
>>> point of view, I'd like to see the equivalent.
>>
>>
>> The argument is for regularity. On a global scale, regularity helps
>> the user, while exceptions, at best, are useful only locally -- an
>> observation that, unfortunately, is difficult to demonstrate with toy
>> examples.
>
> I see. To a large extent, it's also very hard to explain benefits to
> refactoring. I gave an abstract example, Mark gave a more concrete small
> example, but admittedly, none really capture the benefit I have experience
> in a medium-sized Node.js application.

I disagree with all those examples, though. Not from a theoretical
level, but from a "those examples don't show what you want" level.

Unless I'm reading Mark's concrete example wrong, he didn't actually
show any nested promises at all - he showed a function that could take
a plain value or a promise, and wants to always treat it as a promise
(and return a promise). That has nothing to do with nested promises,
and is already addressed by Futures anyway through Future.resolve().
(However, Mark hasn't been able to respond to this thread yet since my
last message, so he may point out something that I missed.)

Your abstract example was:

> If Future<Future<x>> can exist, then you'll have to write this
> boilerplate code in a lot of places:
> f.then(function res(v){
> if(Future.isFuture(v)){
> v.then(res);
> }
> else{
> // actual work with the resolved value.
> }
> })

I don't understand why this boilerplate code has to exist, or why it
does anything useful for you. Fundamentally, your example seems to
show you having to react to a *badly-authored outside environment*,
where somebody fucked up and double-wrapped a value by *accident*.
This is exactly like arguing that Array#map needs to fully flatten, in
case people accidentally pass in [[1, 2, 3]]. Your scare-boilerplate
isn't even complete - if you're scared that the environment might be
broken and doing extra wrapping by accident, it might be doing three
or more levels of wrapping, too, so you've really got a problem on
your hands.

It's impossible for this to happen by accident unless someone's code
is broken. I don't think this is an easy mistake to make, in the
first place, and if it is made, it should be fixed, not papered over
by the language.

The main reason people have given for this potentially happening is
authors mixing promise libraries, and the libraries not recognizing
each other's promises, so .then() doesn't properly unwrap the return
value of its callback. This seems rare on its face (most authors
don't mix major libraries; authors on this list may be an exception),
will be much less common as the standard Futures crowd out
non-standard promises (not only will bespoke library promises be less
common, but whatever recognition mechanism Futures uses will be copied
by libraries, so the non-recognition problem will go away), and should
be fixed by an explicit bandage, such as Future.flatten() or
something, that can be applied by the author as necessary when
crossing library boundaries. Breaking the regularity of the entire
feature for a rare, temporary, author-predictable library-interop
failure seems like a bad trade-off.

If you get stacked promises *on purpose*, then obviously you don't
want to break the feature by having .then() recursively unwrap.

I would *love* to see a concrete example of a nested promise problem
that's not a result of an authoring error (which seems hard to do on
its face - at least with Futures, it seems *difficult* to accidentally
double-wrap), a weird non-JS environment (like Dean Tribble's example,
which turned out to be invalid for JS promises and DOM Futures), or a
mixing of major libraries with bespoke promises that don't mutually
recognize each other (because this will go away reasonably fast now
that we have standardized Futures).

~TJ
David Bruant
2013-04-26 17:02:54 UTC
Permalink
Le 26/04/2013 17:25, Tab Atkins Jr. a ?crit :
> On Fri, Apr 26, 2013 at 3:19 AM, David Bruant <bruant.d at gmail.com> wrote:
> Your abstract example was:
>> If Future<Future<x>> can exist, then you'll have to write this
>> boilerplate code in a lot of places:
>> f.then(function res(v){
>> if(Future.isFuture(v)){
>> v.then(res);
>> }
>> else{
>> // actual work with the resolved value.
>> }
>> })
> I don't understand why this boilerplate code has to exist, or why it
> does anything useful for you. Fundamentally, your example seems to
> show you having to react to a *badly-authored outside environment*,
> where somebody fucked up and double-wrapped a value by *accident*.
No, please read the last part of [1].
A function can change of signature. That happens anytime a then callback
used to return a non-promise value and suddenly returns a promise
because the computation moved from local to remote. So changing from
p.then(v =>{ return someLocalComputation()+v; })

to
p.then(v => { return someRemoteComputation().then(x => x+v) })

Such refactoring happens and isn't an accident.
If flattening doesn't happen, all consumers of the next promise have to
add unwrapping boilerplate.

> This is exactly like arguing that Array#map needs to fully flatten, in
> case people accidentally pass in [[1, 2, 3]].
Some APIs will flatten nested arrays. It depends on the use case.
In that instance, no one has really provided concrete use cases where
that would be useful for promises beyond writing a flattening
abstraction or such.
The best argument I've heard so far was from Andreas and it's solved if
the low-level API is provided.
It doesn't make it less useful to have flattening by default.

> The main reason people have given for this potentially happening is
> authors mixing promise libraries, and the libraries not recognizing
> each other's promises
That's something I didn't talk about during the flattening discussion
(until realizing both issues are actually connected) and chose to
completely ignore as I actually am in disagreement with others on that
point (though I'll probably have to resolve myself to accepting it by
lack of better idea).
My arguments are in the realm of software engineering, like ease of
refactoring, code maintainability, readability.

> If you get stacked promises *on purpose*
What are the use cases where you want that besides when writing a
promise abstraction (like flattening)?

> then obviously you don't
> want to break the feature by having .then() recursively unwrap.
>
> I would *love* to see a concrete example of a nested promise problem
> that's not a result of an authoring error (which seems hard to do on
> its face - at least with Futures, it seems *difficult* to accidentally
> double-wrap)a weird non-JS environment (like Dean Tribble's example,
> which turned out to be invalid for JS promises and DOM Futures), or a
> mixing of major libraries with bespoke promises that don't mutually
> recognize each other (because this will go away reasonably fast now
> that we have standardized Futures).
Refactoring and not having to rewrite/add boilerplate all the promise
consumer.

I would *love* to see a concrete example of a nested promise use case
that isn't an abstraction that better belongs in a library.

David

[1] https://mail.mozilla.org/pipermail/es-discuss/2013-April/030232.html
Claus Reinke
2013-04-26 14:11:33 UTC
Permalink
> Can you point to any code in wide use that makes use of this
> "thenables = monads" idea you seem to be implicitly assuming?
> Perhaps some of this "generic thenable library code"? I have never
> seen such code, whereas the use of "thenable" to mean "object with
> a then method, which we will try to treat as a promise" as in
> Promises/A+ seems widely deployed throughout libraries that are
> used by thousands of people judging by GitHub stars alone.
>
> Thus I would say it's not promise libraries that are "harming the
> thenable operations," but perhaps some minority libraries who
> have misinterpreted what it means to be a thenable.

Instead of rehashing the arguments from the various issue tracker
threads, where examples have been presented even in the half (or
less) I've read so far, let me try a different track: consider the case
of Roman vs Arabic numerals.

As a user of Roman numerals, you might point to centuries of real
world use in the great Roman empire, complain that Arabic numerals
don't have explicit numbers for things like IX or LM etc, ask why anyone
would need an explicit symbol representing nothing at all, or ask for
examples of real world use of Arabic numerals in Roman markets,
or say that Roman numerals don't need to follow the same rules
as Arabic numerals, and that instead users of Arabic numerals have
misinterpreted what it means to work with numbers.

All those arguments are beside the point, though. The point is that
Arabic numerals (with 0) are slightly better than Roman numerals
at representing the structure behind the things they represent, making
it slightly easier to work with those things. And that is why Arabic
numerals have won and Roman numerals are obsolete, after centuries
of real-world use in a great empire.

Thenables in the JS-monadic sense represent common structure
behind a variety of data types and computations, including Promises,
they represent that structure well, and they give JS an equivalent to
vastly successful computational structures in other languages.

And that isn't because I or someone famous says so, but because lots
of people have worked hard for lots of years to figure out what those
common structures are and how they might be represented in
programming languages, refining the ideas against practice until
we have reached a state where the only question is how and when
to translate those ideas to another language, in this case JS.

Promises differ from other thenables, but there is no reason to
burden the common interface with those differences.

Claus
Domenic Denicola
2013-04-26 14:12:40 UTC
Permalink
So there are no such libraries, and you are just wishing that they existed and that they took over the meaning of `then` from promises?
________________________________________
From: Claus Reinke [claus.reinke at talk21.com]
Sent: Friday, April 26, 2013 10:11
To: Domenic Denicola; Mark Miller; David Bruant
Cc: Mark S. Miller; es-discuss
Subject: Re: A Challenge Problem for Promise Designers (was: Re: Futures)

> Can you point to any code in wide use that makes use of this
> "thenables = monads" idea you seem to be implicitly assuming?
> Perhaps some of this "generic thenable library code"? I have never
> seen such code, whereas the use of "thenable" to mean "object with
> a then method, which we will try to treat as a promise" as in
> Promises/A+ seems widely deployed throughout libraries that are
> used by thousands of people judging by GitHub stars alone.
>
> Thus I would say it's not promise libraries that are "harming the
> thenable operations," but perhaps some minority libraries who
> have misinterpreted what it means to be a thenable.

Instead of rehashing the arguments from the various issue tracker
threads, where examples have been presented even in the half (or
less) I've read so far, let me try a different track: consider the case
of Roman vs Arabic numerals.

As a user of Roman numerals, you might point to centuries of real
world use in the great Roman empire, complain that Arabic numerals
don't have explicit numbers for things like IX or LM etc, ask why anyone
would need an explicit symbol representing nothing at all, or ask for
examples of real world use of Arabic numerals in Roman markets,
or say that Roman numerals don't need to follow the same rules
as Arabic numerals, and that instead users of Arabic numerals have
misinterpreted what it means to work with numbers.

All those arguments are beside the point, though. The point is that
Arabic numerals (with 0) are slightly better than Roman numerals
at representing the structure behind the things they represent, making
it slightly easier to work with those things. And that is why Arabic
numerals have won and Roman numerals are obsolete, after centuries
of real-world use in a great empire.

Thenables in the JS-monadic sense represent common structure
behind a variety of data types and computations, including Promises,
they represent that structure well, and they give JS an equivalent to
vastly successful computational structures in other languages.

And that isn't because I or someone famous says so, but because lots
of people have worked hard for lots of years to figure out what those
common structures are and how they might be represented in
programming languages, refining the ideas against practice until
we have reached a state where the only question is how and when
to translate those ideas to another language, in this case JS.

Promises differ from other thenables, but there is no reason to
burden the common interface with those differences.

Claus
David Bruant
2013-04-26 08:36:28 UTC
Permalink
Le 26/04/2013 00:21, Claus Reinke a ?crit :
> I'm still wading through the various issue tracker threads, but only two
> concrete rationales for flattening nested Promises have emerged so far:
>
> 1 "library author doesn't want nested Promises."
> 2 crossing Promise library boundaries can create unwanted nesting
Perhaps you didn't read my post then?
https://mail.mozilla.org/pipermail/es-discuss/2013-April/030192.html
I've shared experience on why flattening promises are convenient (easier
refactoring, easier to reason about) and why non-flattening would be
annoying (impose some sort of boilerplate somewhere to get to the actual
value you're interested in).

Kevin Smith made a point that I believe is underestimated by
non-flattening advocates:
> I think flattening is also tied inextricably to the fact that promises
> are a featureless wrapper for values. Nobody cares about
> promises-as-values because of this featureless-ness. And because they
> are completely uninteresting as values, programmers can think
> "straight through" to the eventual value.
>
> This is highly simplifying for the programmer, especially in the
> context of complex asynchronous data flows.

From experience, I couldn't care less for a wrapper for a wrapper for a
value. I just want wrappers and values. Promises are just async boxes.

Beyond rationale, I'd like non-flattening advocates to show use cases
where a Future<Future<T>> can be useful; more useful than just Future<T>
and T.

David
Claus Reinke
2013-04-26 13:47:59 UTC
Permalink
>> I'm still wading through the various issue tracker threads, but only two
>> concrete rationales for flattening nested Promises have emerged so far:
>>
>> 1 "library author doesn't want nested Promises."
>> 2 crossing Promise library boundaries can create unwanted nesting
> Perhaps you didn't read my post then?
> https://mail.mozilla.org/pipermail/es-discuss/2013-April/030192.html
> I've shared experience on why flattening promises are convenient (easier
> refactoring, easier to reason about) and why non-flattening would be
> annoying (impose some sort of boilerplate somewhere to get to the actual
> value you're interested in).

Yes, I had seen that, but it doesn't explain where those nested Promises
are supposed to come from. For a normal thenable thread (without
implicit flattening or lifting), the nesting level should remain constant -
.of(value) wraps value in a Promise, .then(cb) unwraps the intermediate
result before passing it to cb, and cb constructs a new Promise.

In a later message, you suspect the reason for implicit flattening is
fear of buggy code that may or may not wrap results in Promises. You
say that such code may result from refactoring but, in JS, Promise<value>
is different from value, so trying to hide the level of promise nesting is
likely to hide a bug. Yes, it is more difficult to spot such bugs in JS, but
they need to be fixed nevertheless. Wrapping them in more duct-tape
isn't helping.

> Beyond rationale, I'd like non-flattening advocates to show use cases
> where a Future<Future<T>> can be useful; more useful than just
> Future<T> and T.

My own argument is not for nested futures themselves, but (1) for
futures to offer the same interface (.of, .then) as other thenables, which
(2) implies that there is to be no implicit lifting or flattening in .then.

In other words, you are worried about others giving you arbitrary
nested promises and want to protect against that by implicit flattening,
whereas I want to have control over the level of nesting and keep that
level to one. For promises, I don't expect to use nested promises much,
but I do expect to define and use thenable methods that should work
for promises, too.

Claus
David Bruant
2013-04-26 14:13:19 UTC
Permalink
Le 26/04/2013 15:47, Claus Reinke a ?crit :
> My own argument is not for nested futures themselves, but (1) for
> futures to offer the same interface (.of, .then) as other thenables,
> which
> (2) implies that there is to be no implicit lifting or flattening in
> .then.
> For promises, I don't expect to use nested promises much, but I do
> expect to define and use thenable methods that should work for
> promises, too.
I see. So the underlying problem is the assimilation problem. If any
object with a 'then' method is considered as a promise, then non-promise
values with a 'then' method (that are not supposed to exist according to
Promise/A+, but do anyway in real life [1]) will be mistakenly unwrapped
if flattening semantics is the default...

I was in the naive belief that assimilation and flattening semantics
were disjoint problems :-) Sorry about that.

If I suggest giving up on thenable, Domenic and others are going to
starting throwing rocks at me, so I won't do that :-) Instead:
(messed up) idea: a unique symbol to denote that a value is *not* a
promise. Useful for the flattening semantics to know when to stop *even*
if there is a 'then' method (which is admittedly a rare case anyway and
can justify the use of a specific symbol).

Thoughts?

David

[1] http://casperjs.org/api.html#casper.then
Domenic Denicola
2013-04-26 14:16:52 UTC
Permalink
From: David Bruant [bruant.d at gmail.com]

> Thoughts?

Since this entire problem seems predicated on Claus's misunderstanding of the term "thenable," which apparently has no basis in real libraries but instead entirely in wishful thinking, it might be more prudent for him to use the term "monad" instead of "thenable" and perhaps choose a different name for the method he wants, e.g. "flatMap" or "bind" instead of "then." That seems more aligned with reality than trying to change promise semantics based on some desire to co-opt the word "then" from promise libraries.
Kevin Smith
2013-04-25 17:28:29 UTC
Permalink
I think flattening is also tied inextricably to the fact that promises are
a featureless wrapper for values. Nobody cares about promises-as-values
because of this featureless-ness. And because they are completely
uninteresting as values, programmers can think "straight through" to the
eventual value.

This is highly simplifying for the programmer, especially in the context of
complex asynchronous data flows. It is a valuable property and in a sense
resembles a pure, universal currency.

This suggests a warning: if we admit promise subclassing (in which
subclasses have extra features, such as cancel-ability), then this useful
property goes away, and with it flattening.

{ Kevin }
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130425/90884a95/attachment.html>
Mark Miller
2013-04-25 18:18:41 UTC
Permalink
That's a good point. Neither the E language nor the Q library allow
subclassing of promises. The motivating reason in both cases is the
security properties that promises must provide. But you're right -- this is
an additional benefit. Promises/A+, being a minimalistic codification of
broader agreement, neither prohibits nor demands subclassing.

Both the E language and the Q library instead provide extension mechanisms
other than subclassing[1] which are carefully designed so that such
extended promises cannot violate the promise security properties. These
extension mechanisms are sufficient to create lazy promises -- promises
that compute their resolution of demand, and of course remote objects. This
is how we keep the distributed object system out of the promise foundations
itself, and instead build it as a layer on top.


[1] <http://wiki.erights.org/wiki/Proxy#makeProxy>
the explanation of E's makeProxy on page 10 of <
http://research.google.com/pubs/pub36574.html>
<https://code.google.com/p/es-lab/source/browse/trunk/src/ses/makeQ.js#410>



On Thu, Apr 25, 2013 at 10:28 AM, Kevin Smith <zenparsing at gmail.com> wrote:

> I think flattening is also tied inextricably to the fact that promises are
> a featureless wrapper for values. Nobody cares about promises-as-values
> because of this featureless-ness. And because they are completely
> uninteresting as values, programmers can think "straight through" to the
> eventual value.
>
> This is highly simplifying for the programmer, especially in the context
> of complex asynchronous data flows. It is a valuable property and in a
> sense resembles a pure, universal currency.
>
> This suggests a warning: if we admit promise subclassing (in which
> subclasses have extra features, such as cancel-ability), then this useful
> property goes away, and with it flattening.
>
> { Kevin }
>



--
Text by me above is hereby placed in the public domain

Cheers,
--MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130425/31ef2a9d/attachment.html>
Tab Atkins Jr.
2013-04-26 01:45:19 UTC
Permalink
On Thu, Apr 25, 2013 at 8:57 AM, Mark Miller <erights at gmail.com> wrote:
> The refactoring of putting the "Q(srcP).then" in the deposit method
> unburdened all clients such as the buy method above from doing this
> postponement themselves. The new buy method on page 13 now reads:
>
> buy: (desc, paymentP) => {
> // do whatever with desc, look up $10 price
> return (myPurse ! deposit(10, paymentP)).then(_ => good);
> }
>
> The old deposit method returned undefined or threw. The new deposit method
> itself returns a promise-for-undefined that either fulfills to undefined or
> rejects. However, in both cases, the promise for what the deposit method
> will return remains the same, and so the buy method above did not become
> burdened with having to do a doubly nested then in order to find out whether
> the deposit succeeded. In addition, our first example line of client code
>
> var ackP = paymentP ! deposit(10, myPurse);
>
> did not have to change at all. The Q(srcP).then at the beginning of the
> deposit method will turn a purse into a promise for a purse, but will not
> turn a promise for a purse into a promise for a promise for a purse. The
> ackP remains a one-level promise whose fulfillment or rejection indicates
> whether the deposit succeeds or fails.
>
> Call this refactoring "shifting the burden of postponement".
>
> I hope this gives some sense about why those who've experienced such
> patterns like them. And I hope this provides a concrete and meaningful
> challenge to those who think promises should work otherwise.

This same thing is handled by Future.resolve(), no? If you expect
your function to receive either a value or a Future<value>, you can
just pass it through Future.resolve() to get a guaranteed
Future<value>. It looks like it would result in roughly identical
code to your refactoring - rather than this (taken from your first
code example):

deposit: (amount, srcP) =>
Q(srcP).then(src => {
Nat(balance + amount);
m.get(src)(Nat(amount));
balance += amount;
})

You'd just do this:

deposit: (amount, srcP) =>
Future.resolve(srcP).then(src => {
Nat(balance + amount);
m.get(src)(Nat(amount));
balance += amount;
})

Right?

Based on this example, it looks like the problem you're solving with
recursive-unwrapping is that you speculatively wrap values in a
promise, then rely on .then() to double-unwrap if necessary. If this
is an accurate summary of the problem, then Future.resolve() solves it
better - same code, but better theoretical semantics.

~TJ
Mark S. Miller
2013-04-26 01:49:03 UTC
Permalink
What is the semantics of Future.resolve?




On Thu, Apr 25, 2013 at 6:45 PM, Tab Atkins Jr. <jackalmage at gmail.com>wrote:

> On Thu, Apr 25, 2013 at 8:57 AM, Mark Miller <erights at gmail.com> wrote:
> > The refactoring of putting the "Q(srcP).then" in the deposit method
> > unburdened all clients such as the buy method above from doing this
> > postponement themselves. The new buy method on page 13 now reads:
> >
> > buy: (desc, paymentP) => {
> > // do whatever with desc, look up $10 price
> > return (myPurse ! deposit(10, paymentP)).then(_ => good);
> > }
> >
> > The old deposit method returned undefined or threw. The new deposit
> method
> > itself returns a promise-for-undefined that either fulfills to undefined
> or
> > rejects. However, in both cases, the promise for what the deposit method
> > will return remains the same, and so the buy method above did not become
> > burdened with having to do a doubly nested then in order to find out
> whether
> > the deposit succeeded. In addition, our first example line of client code
> >
> > var ackP = paymentP ! deposit(10, myPurse);
> >
> > did not have to change at all. The Q(srcP).then at the beginning of the
> > deposit method will turn a purse into a promise for a purse, but will not
> > turn a promise for a purse into a promise for a promise for a purse. The
> > ackP remains a one-level promise whose fulfillment or rejection indicates
> > whether the deposit succeeds or fails.
> >
> > Call this refactoring "shifting the burden of postponement".
> >
> > I hope this gives some sense about why those who've experienced such
> > patterns like them. And I hope this provides a concrete and meaningful
> > challenge to those who think promises should work otherwise.
>
> This same thing is handled by Future.resolve(), no? If you expect
> your function to receive either a value or a Future<value>, you can
> just pass it through Future.resolve() to get a guaranteed
> Future<value>. It looks like it would result in roughly identical
> code to your refactoring - rather than this (taken from your first
> code example):
>
> deposit: (amount, srcP) =>
> Q(srcP).then(src => {
> Nat(balance + amount);
> m.get(src)(Nat(amount));
> balance += amount;
> })
>
> You'd just do this:
>
> deposit: (amount, srcP) =>
> Future.resolve(srcP).then(src => {
> Nat(balance + amount);
> m.get(src)(Nat(amount));
> balance += amount;
> })
>
> Right?
>
> Based on this example, it looks like the problem you're solving with
> recursive-unwrapping is that you speculatively wrap values in a
> promise, then rely on .then() to double-unwrap if necessary. If this
> is an accurate summary of the problem, then Future.resolve() solves it
> better - same code, but better theoretical semantics.
>
> ~TJ
>



--
Cheers,
--MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130425/e970b8dd/attachment.html>
Tab Atkins Jr.
2013-04-26 03:38:04 UTC
Permalink
On Thu, Apr 25, 2013 at 6:49 PM, Mark S. Miller <erights at google.com> wrote:
> What is the semantics of Future.resolve?

Creates an already-accepted future using the "resolve" algorithm,
which is the same magic that happens to the return value of a .then()
callback (if it's a future, it adopts the state; otherwise, it accepts
with the value).

In other words, "If this is a future, use it; otherwise, make me a
future for it".

~TJ
Mark S. Miller
2013-04-26 03:52:47 UTC
Permalink
So how does the semantics of Q(x) differ from the semantics of
Future.resolve(x) ?


On Thu, Apr 25, 2013 at 8:38 PM, Tab Atkins Jr. <jackalmage at gmail.com>wrote:

> On Thu, Apr 25, 2013 at 6:49 PM, Mark S. Miller <erights at google.com>
> wrote:
> > What is the semantics of Future.resolve?
>
> Creates an already-accepted future using the "resolve" algorithm,
> which is the same magic that happens to the return value of a .then()
> callback (if it's a future, it adopts the state; otherwise, it accepts
> with the value).
>
> In other words, "If this is a future, use it; otherwise, make me a
> future for it".
>
> ~TJ
>



--
Cheers,
--MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130425/c2dc21b3/attachment.html>
Tab Atkins Jr.
2013-04-26 05:40:04 UTC
Permalink
On Thu, Apr 25, 2013 at 8:52 PM, Mark S. Miller <erights at google.com> wrote:
> So how does the semantics of Q(x) differ from the semantics of
> Future.resolve(x) ?

I suppose you tell me?

You offered, as an example of why recursive unwrapping was useful,
some example code that used Q(val).then(). The surrounding
explanatory text suggested that this helped with the case where "val"
could be either a plain value *or* a promise.

I assumed this meant that Q() simple wraps its argument in a promise
(like Future.accept() does), resulting in either a promise or a
promise-for-a-promise, and .then() recursively unwrapped, so you ended
up with the plain value at the end.

If that's not the case, and Q() does the same "conditional wrapping"
that Future.resolve() does, then I don't understand the point of your
example, or how your OP in this thread supports the assertion that
recursive unwrapping is useful.

~TJ
Kevin Gadd
2013-04-26 08:13:35 UTC
Permalink
Something that wasn't clear to me personally until reading the last few
posts: I suspect that some of the negative reaction to unwrapping/wrapping,
and the suggestion that Future<Future<T>> is a meaningful construct, comes
from the mindset of static typing - not in the sense that static types
themselves are important, but that it feels implicitly 'wrong' or
'incorrect' to write a function that is uncertain as to whether a value is
a future or not - being so incredibly haphazard about asynchronicity. This
is probably the root of my personal opinion that unwrapping isn't a good
thing. I've never seen a scenario like this in the .NET world, perhaps just
because I've never encountered code that was designed with this sort of
fundamental confusion about where asynchrony is necessary.

On the other hand, that kind of haphazardness/laziness is probably a pillar
of successful JS applications and architectures, because it lets you get
things done without arcane, perhaps inexplicable nuances of type systems
and libraries getting in your way. That definitely enables more
applications to be built faster.

Consider this my 2c regardless - the idea of functions being unsure about
whether they need to be asynchronous, because a value might be a future or
might not, and then passing that conceptual ambiguity on down the chain and
on outward into the rest of the application, does bug me. My instincts
suggest that it would lead to less maintainable code and more problems in
the wild for users, even if unwrapping itself is an incredibly useful
feature for these use cases. That is, unwrapping and wrapping are great,
but maybe the fact that they are so essential indicates a real problem that
should be addressed in the design of any standard Future primitive?

On Thu, Apr 25, 2013 at 10:40 PM, Tab Atkins Jr. <jackalmage at gmail.com>wrote:

> On Thu, Apr 25, 2013 at 8:52 PM, Mark S. Miller <erights at google.com>
> wrote:
> > So how does the semantics of Q(x) differ from the semantics of
> > Future.resolve(x) ?
>
> I suppose you tell me?
>
> You offered, as an example of why recursive unwrapping was useful,
> some example code that used Q(val).then(). The surrounding
> explanatory text suggested that this helped with the case where "val"
> could be either a plain value *or* a promise.
>
> I assumed this meant that Q() simple wraps its argument in a promise
> (like Future.accept() does), resulting in either a promise or a
> promise-for-a-promise, and .then() recursively unwrapped, so you ended
> up with the plain value at the end.
>
> If that's not the case, and Q() does the same "conditional wrapping"
> that Future.resolve() does, then I don't understand the point of your
> example, or how your OP in this thread supports the assertion that
> recursive unwrapping is useful.
>
> ~TJ
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
>



--
-kg
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130426/4a539ce7/attachment-0001.html>
Ron Buckton
2013-04-26 04:59:52 UTC
Permalink
I?m not sure I fully grok the use cases for FutureResolver#accept and having Future<Future<value>>. Having to call an Unwrap extension method on a Task<Task<T>> in .NET is an unfortunate necessity. Also, since Future#then implicitly resolves a future it is difficult to return a Future<Future<value>> from a then.

In every case where I've used something like a Future it has always seemed more convenient to have it implicitly unwrap.

For native Futures, I don?t think it makes sense to try and unwrap just any object with a callable ?then?. Its a necessity today for Promise libraries in ES5 as there?s no ideal way to brand an object as a Future.

This is what seems to make sense to me from a practical standpoint when using native Futures:


* If you resolve a Future (A) with a Future (B), the result of Future (A) should be B.
* This implicit unwrap should only work out of the box for Future subclasses or branded Futures.
* To coerce a ?thenable? should be an explicit opt-in (e.g. Q(), or Future.of).
* There should be a well-defined mechanism for chaining futures from subclasses to preserve capabilities (ProgressFuture, etc.). One option might be a FutureFactory, another is to have subclasses override the .then method.
* An alternative to FutureResolver#accept that would allow for an explicit Future for a Future, might be to box the future, either explicitly (e.g. resolver.resolve({ future: f })) or with something like a Future.box() that encapsulates the future. In this way, if you need a future for a future you can support it on both FutureResolver and ?then?.


A Future for a Future seems like a corner case compared to the broader simplicity of an implicit unwrap.

If we had Future.box() instead of FutureResolver#accept, we might be able to do things like:

function someFutureFutureV() {
return new Future(function (resolver) {
var F = someFutureV();
var Fboxed = Future.box(F); // some special instance with a .value property?
// F === Fboxed.value;
resolver.resolve(Fboxed); // i.e. resolver.resolve(Future.box(F)) instead of resolver.accept(F)
})
}

someFutureFutureV().then(function (F) {
// ?then? unboxes Fboxed, just as it might have unwrapped F were it not boxed.
// ...
return Future.box(F); // another Fboxed
}).then(function (F) {
// F is again preserved, this time from a call to then
// ...
return F; // no boxing this time
}).then(function (V) {
// F is now unwrapped to V
});

While slightly more complicated for the FutureFuture case, the expectations are simpler for the broader usage scenarios, and the API surface is simpler (no FutureResolver#accept) for the most common use cases. If you really do intend to have a FutureFuture, Future.box would let you have a single way to opt in for both FutureResolver#resolve as well as Future#then.

Ron


Sent from Windows Mail

From: Tab Atkins Jr.
Sent: ?Thursday?, ?April? ?25?, ?2013 ?8?:?38? ?PM
To: Mark S. Miller
Cc: Mark Miller, es-discuss

On Thu, Apr 25, 2013 at 6:49 PM, Mark S. Miller <erights at google.com> wrote:
> What is the semantics of Future.resolve?

Creates an already-accepted future using the "resolve" algorithm,
which is the same magic that happens to the return value of a .then()
callback (if it's a future, it adopts the state; otherwise, it accepts
with the value).

In other words, "If this is a future, use it; otherwise, make me a
future for it".

~TJ
_______________________________________________
es-discuss mailing list
es-discuss at mozilla.org
https://mail.mozilla.org/listinfo/es-discuss

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130426/144b719b/attachment.html>
Claus Reinke
2013-04-26 08:40:07 UTC
Permalink
> A Future for a Future seems like a corner case compared to the
> broader simplicity of an implicit unwrap.

The argument is not about whether Future<Future<...>> is a common
case. The Argument is that Future<...> and Array<...> and Optional<...>
and things that may raise catchable errors and other types have enough
structure in common that it makes sense to write common library code
for them.

One example is a map method, other examples may need more structure -
eg, filter would need a way to represent empty structures, so not all
wrapper types can support filter.

The goal is to have types/classes with common structure implement
common interfaces that represent their commonalities. On top of those
common interfaces, each type/class will have functionality that is not
shared with all others. As long as the common and non-common
interfaces are clearly separated, that is not a problem.

It is only when non-common functionality is mixed into what could
be common interface methods, for convenience in the non-common
case, that a type/class loses the ability to participate in code written
to the common interface. That is why recursive flattening and implicit
lifting of Promises is okay, as long as it isn't mixed into 'then'.

Claus
Bill Frantz
2013-04-26 13:03:57 UTC
Permalink
Let me take a crack at describing E's support for promises.

E has two modes for sending a message to an object. There is the
immediate send and the eventual send. If the Object is an
unresolved promise the immediate send will trap. (A promise can
be forced to resolve using the "when" construct.)

If the program uses eventual sends, then unresolved promises act
like any other value. This allows operations such as:

Object A on machine 1
Object B on machine 2

A sends to B getting back object C also hosted on machine 2
A sends to C getting back a final result.

The way this is implemented is that when A sends to B, a promise
for the result C is constructed. When A sends to C, that promise
is used for the send and a promise for the final result is
constructed. The message to C is sent without waiting for the
response from the message to B, eliminating one round trip
communication delay.

If A needs to use the final result in an immediate operation, it
will wait for the final result promise to be resolved using the
"when" construct.

Cheers - Bill

-----------------------------------------------------------------------
Bill Frantz | I like the farmers' market | Periwinkle
(408)356-8506 | because I can get fruits and | 16345
Englewood Ave
www.pwpconsult.com | vegetables without stickers. | Los Gatos,
CA 95032
Mark S. Miller
2013-04-26 16:43:24 UTC
Permalink
Hi Bill,

I think I know what you mean by these terms, and what I think you mean is
correct ;). But given the history of promises and futures, you make two
unfortunate and confusing terminology choices: "forced" and "wait".
Instead, E promises, like all JS promises, are inherently non-blocking. The
"when" (aka "then") doesn't "force" or "wait" or block, it registers the
callback[s] to be called in a later turn of the event loop sometime after
the promise they are registered on is fulfilled or broken (aka "fulfilled"
or "rejected"). The "when" immediately returns a promise for what the
invoked callback will return. In all these ways, E promises are like modern
JS promises.

None of this however addresses the prior question about E promises. E
promises were inspired by Joule Channels and Xanadu promises, both of which
were at least as influenced by Concurrent Prolog logic variables as they
were by previous promise/future systems[1]. As with logic variables, once
an E promise is fulfilled with a value, it is that value, i.e., it is not
observably different from that value. The E equality tests will judge it to
be identical to its fulfillment. In Actor or Smalltalk terms, one could say
it becomes that value. This was viable in E for reasons not available in
JS, and so a JS promise fulfilled with a value remains distinct from the
value itself. The formalism that originally inspired modern JS promises is
thus the logic variables of concurrent logic programming; so the similarity
of the resulting abstractions in some ways to monads surprised me.

The reason this works in E is indeed touched on by your email -- an
unresolved (aka "pending") E promise is a kind of reference, not a kind of
object, and so does not have its own methods. Using "." on a pending E
promise is an error. Using "<-" (aka "!") on an E promise invokes the
object it is a promise for, rather than invoking the promise itself[2].
"when" is something one does to a promise, not something one asks a promise
to do. It is the extreme form of the "featureless"ness that Kevin raises.


[1] See Chapter 23 of <http://erights.org/talks/thesis/markm-thesis.pdf>
"From Objects to Actors and Back Again"

[2] Not quite true. There are three eventual messages that are a visible
part of the promise infrastructure: __whenMoreResolved, __whenBroken, and
__reactToLostClient. The first two are understood by the promises. The last
is emitted by promises on partition. But none of these are part of the
normal use experience -- hence the two initial underbars in their names. JS
promises achieve the needs these serve by other means.




On Fri, Apr 26, 2013 at 6:03 AM, Bill Frantz <frantz at pwpconsult.com> wrote:

> Let me take a crack at describing E's support for promises.
>
> E has two modes for sending a message to an object. There is the immediate
> send and the eventual send. If the Object is an unresolved promise the
> immediate send will trap. (A promise can be forced to resolve using the
> "when" construct.)
>
> If the program uses eventual sends, then unresolved promises act like any
> other value. This allows operations such as:
>
> Object A on machine 1
> Object B on machine 2
>
> A sends to B getting back object C also hosted on machine 2
> A sends to C getting back a final result.
>
> The way this is implemented is that when A sends to B, a promise for the
> result C is constructed. When A sends to C, that promise is used for the
> send and a promise for the final result is constructed. The message to C is
> sent without waiting for the response from the message to B, eliminating
> one round trip communication delay.
>
> If A needs to use the final result in an immediate operation, it will wait
> for the final result promise to be resolved using the "when" construct.
>
> Cheers - Bill
>
> ------------------------------**------------------------------**
> -----------
> Bill Frantz | I like the farmers' market | Periwinkle
> (408)356-8506 | because I can get fruits and | 16345 Englewood Ave
> www.pwpconsult.com | vegetables without stickers. | Los Gatos, CA 95032
>
>


--
Cheers,
--MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130426/51acd55a/attachment.html>
Bill Frantz
2013-04-26 22:47:48 UTC
Permalink
Thanks for making these issues clearer to me Mark. I am
beginning to get some idea of the problems that JS promises face
given that they must have an object-like appearance.

I like the fact that in E, promises "just work" and I think of
Joule as a language where every value is a promise and
computation will take place when the data is ready.

Cheers - Bill

On 4/26/13 at 9:43 AM, erights at google.com (Mark S. Miller) wrote:

>Hi Bill,
>
>I think I know what you mean by these terms, and what I think you mean is
>correct ;). But given the history of promises and futures, you make two
>unfortunate and confusing terminology choices: "forced" and "wait".
>Instead, E promises, like all JS promises, are inherently non-blocking. The
>"when" (aka "then") doesn't "force" or "wait" or block, it registers the
>callback[s] to be called in a later turn of the event loop sometime after
>the promise they are registered on is fulfilled or broken (aka "fulfilled"
>or "rejected"). The "when" immediately returns a promise for what the
>invoked callback will return. In all these ways, E promises are like modern
>JS promises.
>
>None of this however addresses the prior question about E promises. E
>promises were inspired by Joule Channels and Xanadu promises, both of which
>were at least as influenced by Concurrent Prolog logic variables as they
>were by previous promise/future systems[1]. As with logic variables, once
>an E promise is fulfilled with a value, it is that value, i.e., it is not
>observably different from that value. The E equality tests will judge it to
>be identical to its fulfillment. In Actor or Smalltalk terms, one could say
>it becomes that value. This was viable in E for reasons not available in
>JS, and so a JS promise fulfilled with a value remains distinct from the
>value itself. The formalism that originally inspired modern JS promises is
>thus the logic variables of concurrent logic programming; so the similarity
>of the resulting abstractions in some ways to monads surprised me.
>
>The reason this works in E is indeed touched on by your email -- an
>unresolved (aka "pending") E promise is a kind of reference, not a kind of
>object, and so does not have its own methods. Using "." on a pending E
>promise is an error. Using "<-" (aka "!") on an E promise invokes the
>object it is a promise for, rather than invoking the promise itself[2].
>"when" is something one does to a promise, not something one asks a promise
>to do. It is the extreme form of the "featureless"ness that Kevin raises.
>
>
>[1] See Chapter 23 of <http://erights.org/talks/thesis/markm-thesis.pdf>
>"From Objects to Actors and Back Again"
>
>[2] Not quite true. There are three eventual messages that are a visible
>part of the promise infrastructure: __whenMoreResolved, __whenBroken, and
>__reactToLostClient. The first two are understood by the promises. The last
>is emitted by promises on partition. But none of these are part of the
>normal use experience -- hence the two initial underbars in their names. JS
>promises achieve the needs these serve by other means.
>
>
>
>
>On Fri, Apr 26, 2013 at 6:03 AM, Bill Frantz <frantz at pwpconsult.com> wrote:
>
>>Let me take a crack at describing E's support for promises.
>>
>>E has two modes for sending a message to an object. There is the immediate
>>send and the eventual send. If the Object is an unresolved promise the
>>immediate send will trap. (A promise can be forced to resolve using the
>>"when" construct.)
>>
>>If the program uses eventual sends, then unresolved promises act like any
>>other value. This allows operations such as:
>>
>>Object A on machine 1
>>Object B on machine 2
>>
>>A sends to B getting back object C also hosted on machine 2
>>A sends to C getting back a final result.
>>
>>The way this is implemented is that when A sends to B, a promise for the
>>result C is constructed. When A sends to C, that promise is used for the
>>send and a promise for the final result is constructed. The message to C is
>>sent without waiting for the response from the message to B, eliminating
>>one round trip communication delay.
>>
>>If A needs to use the final result in an immediate operation, it will wait
>>for the final result promise to be resolved using the "when" construct.
>>
>>Cheers - Bill
>>
>>------------------------------**------------------------------**
>>-----------
>>Bill Frantz | I like the farmers' market | Periwinkle
>>(408)356-8506 | because I can get fruits and | 16345 Englewood Ave
>>www.pwpconsult.com | vegetables without stickers. | Los Gatos, CA 95032
>>
>>
>
>
---------------------------------------------------------------------------
Bill Frantz | "I wish there was a knob on the TV to turn
up the
408-356-8506 | intelligence. There's a knob called
"brightness", but
www.pwpconsult.com | it doesn't work. -- Gallagher
Loading...