Open paldepind opened 7 years ago
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.
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.
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.
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
@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.
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.
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 :smile: 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.
An expression
expr
is referentially transparent if in a programp
, all occurrences ofexpr
inp
can be replaced by an assignment toexpr
without effecting an observable change inp
.
That's what I mean, if that's not what you mean, then ... erm...
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.
Side effects inside none-pure code. Those are not referential transparent so aren't equal.
Excellent talk about it here : https://www.youtube.com/watch?v=qBvFsA3dglk Slides are here : http://yowconference.com.au/slides/yowwest2016/Morris-Parametricity.pdf
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.
@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 IO
s 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.
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.
@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 :sweat_smile:
The readme says:
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.