Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Promise equality #211

Open
paldepind opened this issue Dec 15, 2016 · 15 comments
Open

Promise equality #211

paldepind opened this issue Dec 15, 2016 · 15 comments

Comments

@paldepind
Copy link

The readme says:

Two promises are equivalent when they yield equivalent values.

Is this really an "appropriate definition of equivalence for the given value"? Because then I don't understand what the criteria for equivalence is?

It seems to me that a promise that resolves in five minutes with the value "Tada" is not equal to a promise that resolves tomorrow afternoon with the value "Tada". I'd say that two promises are only equal if they resolve to equivalent values at the exact same times.

@paldepind paldepind changed the title Promise equlity Promise equality Dec 15, 2016
@SimonRichardson
Copy link
Member

I do get the confusion, but new Promise() != new Promise() so the only way to really know if the promises are truly the same are to see the outcomes (apart for testing the instances are the same). I personally think we could improve that sentence and mention something along the lines (draft, don't hate me), Two promises are equivalent if they are referentially transparent for the outcome of the promises. or more elegantly put.

@bergus
Copy link
Contributor

bergus commented Dec 15, 2016

The problem is that resolution timing is inherently a side-effect. In any pure monad, we'd expect

const ma = f();
const mb = g().chain(() => ma);

and

const mb = g().chain(() => f());

to have the same result mb (though not necessarily the same execution efficiency). This does not work with promises if we consider their impure timing to be relevant, so we choose to ignore it in the equivalence relation.

@SimonRichardson
Copy link
Member

SimonRichardson commented Dec 15, 2016

Two promises are equivalent if they are referentially transparent.

Maybe it should say that then, because that covers all edge cases. If you don't think it does then you need to read more.

@safareli
Copy link
Member

What if we remove that line?

If we say Two promises are equivalent if they are referentially transparent. we can remove promise and just state: Two values are equivalent if they are referentially transparent

@paldepind
Copy link
Author

@SimonRichardson

I do get the confusion, but new Promise() != new Promise() so the only way to really know if the promises are truly the same are to see the outcomes (apart for testing the instances are the same).

Well, my point is that just seeing the outcomes is really not enough to know if they are truly the same. To do that you'd have to know both the outcome and the time of resolution.

@bergus

This does not work with promises if we consider their impure timing to be relevant, so we choose to ignore it in the equivalence relation.

Then I think I'd be better to just say that Promises aren't monads because they don't satisfy the laws instead of adopting an IMO too loose sense of equality.

@safareli

I think that is a great idea. It's completely general. However, the current text does already talk about referential transparency even though it doesn't use the term directly:

The definition should ensure that the two values can be safely swapped out in a program that respects abstractions.

@SimonRichardson
Copy link
Member

SimonRichardson commented Dec 16, 2016

Well, my point is that just seeing the outcomes is really not enough to know if they are truly the same. To do that you'd have to know both the outcome and the time of resolution.

I don't think it's important if they're referential transparent. It might help understand more about why it concerns you if they are equal, what's the use case?

I think that is a great idea. It's completely general. However, the current text does already talk about referential transparency even though it doesn't use the term directly:

We should.

@paldepind
Copy link
Author

paldepind commented Dec 16, 2016

I don't think it's important if they're referential transparent.

They're not referential transparent if they resolve at different times. I can easily tell the difference by calling then on them and observe that the function passed to then is called at different times.

It might help understand more about why it concerns you if they are equal, what's the use case?

There is no use case 😄 I just read that part of the readme and found the definition misleading. A promise isn't isomorphic to the value it resolves to and thus it can't be compared for equality just by comparing the value it resolves to.

@SimonRichardson
Copy link
Member

An expression expr is referentially transparent if in a program p, all occurrences of expr in p can be replaced by an assignment to expr without effecting an observable change in p.

That's what I mean, if that's not what you mean, then ... erm...

@paldepind
Copy link
Author

paldepind commented Dec 16, 2016

These two promise resolves to the same value:

const a = new Promise((res) => setTimeout(() => res("Tada"), 6000));
const b = new Promise((res) => setTimeout(() => res("Tada"), 1000));

Are they equal? No, because the value that the promise c resolves to in the program below clearly changes if we replace a with b.

const c = a.then((_) => Date.now());

I.e. there is an observable difference between a and b. Replacing a with b changes the behavior of the program.

@SimonRichardson
Copy link
Member

SimonRichardson commented Dec 16, 2016

Side effects inside none-pure code. Those are not referential transparent so aren't equal.

@SimonRichardson
Copy link
Member

@rpominov
Copy link
Member

Just to add two cents, I think with all these equality definitions it's really depends on what we've chosen to care about in a particular case. And the example with promises is valid, if we don't care about time spend (when we need it to only resolve eventually with a particular value).

Also example with Date.now() is kinda cheating, because to read current time is a side effect. By the same reasoning a = [1] is not equal to b = [1], because a.map((_) => window.performance.now()) is not equal to b.map((_) => window.performance.now()).

Or we can argue that a is not equal to b in the following program:

const a = [1]
const b = [1]

const map = new Map()
map.set(a, 1)
map.set(b, 2)

map.get(a) + 1 // if we replace `a` with `b` this expression will have different result 

So it always depends on what we care about, and in my opinion example with Promises is good enough.

@paldepind
Copy link
Author

@SimonRichardson

Side effects inside none-pure code. Those are not referential transparent so aren't equal.

Let me phrase my argument from another angle. Are two IOs in Haskell equal if they compute the same value? No. Because one IO may generate a random number and return 6 while another one may fire 6 missiles and return 6. When talking about equality of IO values we have to consider the imperative computation that they represent.

In general two monadic values are only equal if they contain the same value and represent the same effects. Promises are the same. When talking about their equality we have to consider their effects which is their resolution/rejection time.

@joneshf
Copy link
Member

joneshf commented Jan 15, 2017

Maybe it's enough for the readme to say that those are example interpretations of equivalence and not definitions for all data types. I don't think the intent is to have an authoritative source for what constitutes equivalence. Rather I think the intent is to provide a couple of examples to spur intuition. I'm sure you could take exception to any of them if you tried hard enough.

For instance, the function example could be picked apart as well.

const foo = x => { const _ = R.range(1, 1000); return x; }

Is foo equivalent to x => x? By the example, they are equivalent. If you factor in the time to compute (which is an observable side effect), then they are not equivalent. Similarly, if you look at the memory used by both functions (another observable side effect), then they are not equivalent.

@paldepind
Copy link
Author

@SimonRichardson

Side effects inside none-pure code. Those are not referential transparent so aren't equal.

So perhaps this is a better example to illustrate that promises are not referentially transparent even if they evaluate to the same value.

const a1 = // promise that resolves to "a" after 2 minutes
const a2 = // promise that resolves to "a" after 4 minutes
const b = // promise that resolves to "b" after 3 minutes

const expr1 = Promise.race([a1, b]); // resolves to "a"
const expr2 = Promise.race([a2, b]); // resolves to "b"

If a1 and a2 are referentially transparent then expr1 and expr2 should be equivalent. But they're not. I think the example highlights a significant problem with the current wording in the spec. By that definition of "equivalent" replacing a promise with an equivalent promise can change a program into a different program. That is not a good definition of equivalent.

Anyway, sorry for commenting on an old issue. #288 reminded me of it 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants