Mark Miller
2013-04-25 15:57:38 UTC
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>
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>