A Challenge Problem for Promise Designers (was: Re: Futures)

classic Classic list List threaded Threaded
90 messages Options
12345
Reply | Threaded
Open this post in threaded view
|

A Challenge Problem for Promise Designers (was: Re: Futures)

Mark Miller-2
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 <[hidden email]> wrote:
Le 24/04/2013 19:41, Andreas Rossberg a écrit :
On 24 April 2013 19:20, Mark S. Miller <[hidden email]> wrote:
On Wed, Apr 24, 2013 at 10:14 AM, Tab Atkins Jr. <[hidden email]>
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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss



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

  Cheers,
  --MarkM

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: A Challenge Problem for Promise Designers (was: Re: Futures)

Claus Reinke
> 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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: A Challenge Problem for Promise Designers (was: Re: Futures)

Kevin Smith
In reply to this post by Mark Miller-2
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 }

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: A Challenge Problem for Promise Designers (was: Re: Futures)

Mark Miller-2
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.


the explanation of E's makeProxy on page 10 of <http://research.google.com/pubs/pub36574.html>



On Thu, Apr 25, 2013 at 10:28 AM, Kevin Smith <[hidden email]> 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

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: A Challenge Problem for Promise Designers (was: Re: Futures)

Claus Reinke
In reply to this post by Claus Reinke
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
> [hidden email]
> https://mail.mozilla.org/listinfo/es-discuss 
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

RE: A Challenge Problem for Promise Designers (was: Re: Futures)

Domenic Denicola-2
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: [hidden email]
Sent: ‎4/‎25/‎2013 18:21
To: [hidden email]; [hidden email]
Cc: [hidden email]; [hidden email]
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
> [hidden email]
> https://mail.mozilla.org/listinfo/es-discuss
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: A Challenge Problem for Promise Designers (was: Re: Futures)

Dean Tribble-2
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 <[hidden email]> 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: [hidden email]
Sent: 4/25/2013 18:21
To: [hidden email]; [hidden email]
Cc: [hidden email]; [hidden email]
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
> [hidden email]
> https://mail.mozilla.org/listinfo/es-discuss
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss



_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: A Challenge Problem for Promise Designers (was: Re: Futures)

Tab Atkins Jr.
On Thu, Apr 25, 2013 at 4:30 PM, Dean Tribble <[hidden email]> 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
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: A Challenge Problem for Promise Designers (was: Re: Futures)

Dean Tribble-2
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. <[hidden email]> 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....

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: A Challenge Problem for Promise Designers (was: Re: Futures)

Tab Atkins Jr.
On Thu, Apr 25, 2013 at 6:03 PM, Dean Tribble <[hidden email]> 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
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: A Challenge Problem for Promise Designers (was: Re: Futures)

Tab Atkins Jr.
In reply to this post by Mark Miller-2
On Thu, Apr 25, 2013 at 8:57 AM, Mark Miller <[hidden email]> 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
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: A Challenge Problem for Promise Designers (was: Re: Futures)

Mark S. Miller-2
What is the semantics of Future.resolve?




On Thu, Apr 25, 2013 at 6:45 PM, Tab Atkins Jr. <[hidden email]> wrote:
On Thu, Apr 25, 2013 at 8:57 AM, Mark Miller <[hidden email]> 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

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: A Challenge Problem for Promise Designers (was: Re: Futures)

Tab Atkins Jr.
On Thu, Apr 25, 2013 at 6:49 PM, Mark S. Miller <[hidden email]> 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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: A Challenge Problem for Promise Designers (was: Re: Futures)

Mark S. Miller-2
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. <[hidden email]> wrote:
On Thu, Apr 25, 2013 at 6:49 PM, Mark S. Miller <[hidden email]> 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

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: A Challenge Problem for Promise Designers (was: Re: Futures)

Ron Buckton-2
In reply to this post by Tab Atkins Jr.
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 <[hidden email]> 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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: A Challenge Problem for Promise Designers (was: Re: Futures)

Tab Atkins Jr.
In reply to this post by Mark S. Miller-2
On Thu, Apr 25, 2013 at 8:52 PM, Mark S. Miller <[hidden email]> 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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: A Challenge Problem for Promise Designers (was: Re: Futures)

Kevin Gadd
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. <[hidden email]> wrote:
On Thu, Apr 25, 2013 at 8:52 PM, Mark S. Miller <[hidden email]> 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
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss



--
-kg

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: A Challenge Problem for Promise Designers (was: Re: Futures)

David Bruant-5
In reply to this post by Claus Reinke
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
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: A Challenge Problem for Promise Designers (was: Re: Futures)

Claus Reinke
In reply to this post by Ron Buckton-2
> 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
 
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: A Challenge Problem for Promise Designers (was: Re: Futures)

David Bruant-5
In reply to this post by Tab Atkins Jr.
Le 26/04/2013 03:39, Tab Atkins Jr. a écrit :

> On Thu, Apr 25, 2013 at 6:03 PM, Dean Tribble <[hidden email]> 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
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
12345