W3C home > Mailing lists > Public > public-script-coord@w3.org > April to June 2013

Re: The Paradox of Partial Parametricity

From: Mark S. Miller <erights@google.com>
Date: Fri, 10 May 2013 05:55:43 -0700
Message-ID: <CABHxS9iO7-tdWSWRF58MPGr8J5ktinE85BTRvwA6f-Tb8XP2qA@mail.gmail.com>
To: Jonas Sicking <jonas@sicking.cc>
Cc: "Tab Atkins, Jr." <jackalmage@gmail.com>, Domenic Denicola <domenic@domenicdenicola.com>, "public-script-coord@w3.org" <public-script-coord@w3.org>, es-discuss <es-discuss@mozilla.org>
[+es-discuss]

I didn't realize that I composed this in reply to a message only on
public-script-coord. Further discussion should occur only on es-discuss.
Sorry for the confusion.


On Fri, May 10, 2013 at 5:52 AM, Mark S. Miller <erights@google.com> wrote:

> I think the key exchange in these threads was
>
> On Fri, May 3, 2013 at 4:17 PM, Jonas Sicking <jonas@sicking.cc> wrote:
> [...]
>
>> I.e. permitting nested promises creates a more complex model and with
>> that you always get more confusion and more questions.
>>
>
>  On Sat, May 4, 2013 at 1:48 AM, Claus Reinke <claus.reinke@talk21.com>
>  wrote:
> [...]
>
>> From the perspective of a-promise-is-just-like-other-**wrapper-classes,
>> auto-flattening promises creates a more complex model
>
>
>
> Together with each of their explanations about what they meant. They are
> both right. Lifting and auto-lifting do not mix. Q Promises give us
> autolifting with no lifting. Monadic promises would give us lifting but no
> autolifting. Having both in one system creates a mess which will lead
> programmers into the particular pattern of bugs Jonas warns about in his
> message.
>
> For clarity I define the following APIs so I can define the three
> architectural choices in terms of the subset of these APIs they contain.
> Obviously, I do not intend to start a bikeshed yet on the particular names
> chosen for these operations. Let's stay focused on semantics, not
> terminology.
>
> An upper case type variable, e.g. T, is fully parametric. It may be a
> promise or non-promise.
> A lower case type variable, e.g. t, is constrained to be a non-promise. If
> you wish to think in conventional type terms, consider Any the top type
> immediately split into Promise and non-promise. Thus type parameter t is
> implicitly constrained to be a subtype of non-promise.
>
>
>
>
> Ref<T> is the union type of T and Promise<T>.
>
> Q.fulfill(T) -> Promise<T> // the unconditional lift operator
>
> Q(Ref<t>) -> Promise<t> // the autolift operator
>
> p.map: Promise<T> -> (T -> U) -> Promise<U>
>
> p.flatMap: Promise<T> -> (T -> Promise<U>) -> Promise<U>
> // If the onSuccess function returns a non-promise, this would throw an
> Error,
> // so this type description remains accurate for the cases which succeed.
>
> p.then: Promise<T> -> (T -> Ref<u>) -> Promise<u>
> // Ignoring the onFailure callback
>
>
>
> * A Monadic promise system would have Q.fulfill, p.map, and p.flatMap.
>
> * A Q-like promise system would have Q, and p.then
>
> * The dominant non-Q-like proposal being debated in these threads has
> Q.fulfill, Q, and p.then.
>
>
>
>
> This note explains why I believe the last is much worse than either of the
> other two choices. As Jonas points out, generic code, to be useful in this
> mixed system, has to be careful and reliable about how much it wraps or
> unwraps the payloads it handles generically. Had programmers been armed
> with .map and .flatMap, they could succeed reliably. Arming them only with
> .then will lead to the astray. As an example, let's start with a variant of
> Sam's async table abstraction. The parts in angle brackets or appearing as
> type declarations only documents what we imagine the programmer may be
> thinking, to be erased to get the real code they wrote. In this
> abstraction, only promises for keys are provided. The get operation
> immediately returns a promise for the value that will have been set. (An
> interesting variant is a table that works even when the get arrives first.
> But we can ignore this wrinkle here.)
>
>
>     class AsyncTable<T,U> {
>         constructor() {
>             this.m = Map<T,U>(); // encapsulation doesn't matter for this
> example
>         }
>         set(keyP :Promise<T>, val :U) {
>             keyP.then(key => { this.m.set(key, val) });
>         }
>         get(keyP :Promise<T>) :Promise<U> {
>             return keyP.then(key => this.m.get(key));
>         }
>     }
>
> When U is a non-promise, the code above works as intended. The .then in
> the .get method was written to act as .map. When tested with U's that are
> not promises, it works fine. But when U is actually Promise<V>, the .get
> method above returns Promise<V> rather than Promise<U>. As far as the
> author is concerned, the .then is functioning as a broken .map in this
> case, with the signature
>
> p.then: Promise<T> -> (T -> U) -> Promise<V>  // WTF?
>
>
> This same abstraction in a monadic promise system would be written
>
>     class AsyncTable<T,U> {
>         constructor() {
>             this.m = Map<T,U>(); // encapsulation doesn't matter for this
> example
>         }
>         set(keyP :Promise<T>, val :U) {
>             keyP.map(key => { this.m.set(key, val) });
>         }
>         get(keyP :Promise<T>) :Promise<U> {
>             return keyP.map(key => this.m.get(key));
>         }
>     }
>
> This works reliably.
>
>
> This same abstraction in a Q-like promise system would be written
>
>     class AsyncTable<t,u> {
>         constructor() {
>             this.m = Map<t,u>(); // encapsulation doesn't matter for this
> example
>         }
>         set(keyP :Promise<t>, val :u) {
>             keyP.then(key => { this.m.set(key, val) });
>         }
>         get(keyP :Promise<t>) :Promise<u> {
>             return keyP.then(key => this.m.get(key));
>         }
>     }
>
> This works reliably. After erasure, note that this is exactly the same as
> the first example. The difference is that the code now correctly implements
> the annotated types representing the programmer's intention.
>
> In a system with autolifting, you can't get full parametricity of promises
> simply by adding an unconditional lift. You have to remove, or at least
> strongly discourage, autolifting. Or you have to take pains to carefully
> work around it.
>
> The main failure mode of standards bodies is to resolve a conflict by
> adding the union of the advocated features. Here, this works even worse
> than it usually does. The coherence of lifting depends on the absence of
> autolifting, *and vice versa*. We need to make a choice.
>
>
> --
>     Cheers,
>     --MarkM
>



-- 
    Cheers,
    --MarkM
Received on Friday, 10 May 2013 12:56:13 UTC

This archive was generated by hypermail 2.3.1 : Tuesday, 6 January 2015 21:37:49 UTC