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

Tab Atkins Jr. jackalmage at gmail.com
Thu Apr 25 18:45:19 PDT 2013


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


More information about the es-discuss mailing list