Proposal-pattern-matching: Why `Object.is` rather than `SameValueZero`?

Created on 13 Sep 2018  Â·  14Comments  Â·  Source: tc39/proposal-pattern-matching

Most non-legacy things within the spec currently, including Map, Set, and Array.prototype.includes, use SameValueZero, which is like Object.is, except it treats +0 and -0 as identical. Is there a reason this doesn't do the same?

champion group discussion

Most helpful comment

@ljharb @isiahmeadows Somebody has already created SameValueSet, a set that is backed by an ECMAScript Set but effectively uses SameValue for discrimination instead of SameValueZero. It's order-preserving and everything. The same can be done for Maps.

I don't see Map.prototype.set.call(customMap, ...) as a valid use since they're different things. It's as nonsensical as RegExp.prototype.test.call(customMap, ...).

All 14 comments

my 2 cents: imo using SameValueZero there was a mistake - it makes Maps and Sets not Functors (and it makes it impossible to build a subclass that robustly doesn't treat +0 and -0 the same). It's easy to treat them the same - what's hard is treating them differently - so the spec should imo be treating them differently.

@ljharb Sets can't be functors no matter what their keys are - there's no way to define a generic, structure-preserving map, because there's always the possibility of new duplicates getting lost. Maps can only be functors given a known set of keys, but:

  1. It doesn't matter what that set of keys is (and yes, it can include +0 and -0 seen as duplicates).
  2. Maps can only be functors over their values - keys can't be directly involved for similar reasons why sets can't be functors.

You can still iterate them, but being a functor isn't a requirement for being iterable.

FWIW, Haskell maps and sets are defined like this:

I probably shouldn't have even mentioned functors - simply the fact that you can't build a more discriminating subclass is why the base class should have differentiated between -0 and +0.

Who says you couldn't override all the methods and add an extra private
field? Boilerplatey, but it works, and I've done similar in the past when I
needed to override some internal behavior.
On Thu, Sep 13, 2018 at 02:27 Jordan Harband notifications@github.com
wrote:

I probably shouldn't have even mentioned functors - simply the fact that
you can't build a more discriminating subclass is why the base class should
have differentiated between -0 and +0.

—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
https://github.com/tc39/proposal-pattern-matching/issues/121#issuecomment-420898009,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AERrBPRdN-aFeG44YkWwHCnJoyT9j3Ubks5uafrngaJpZM4WmkhA
.

@isiahmeadows because Map.prototype.set.call on a subclass will always conflate -0 and +0.

Not if you check beforehand that 1/x === -1/0 and use the private property
for the value in that case instead of delegating...
On Thu, Sep 13, 2018 at 03:01 Jordan Harband notifications@github.com
wrote:

@isiahmeadows https://github.com/isiahmeadows because
Map.prototype.set.call on a subclass will always conflate -0 and +0.

—
You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub
https://github.com/tc39/proposal-pattern-matching/issues/121#issuecomment-420904675,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AERrBOFHhlVMHVygHO-iSuPHRo80m_tCks5uagKOgaJpZM4WmkhA
.

@isiahmeadows Note that the comment you're responding to was about Map.prototype.set.call(customMap, ...). That bypasses your overridden methods; there's no way to interpose a check against that.

(Unless you just ignore your own MapData entirely and store all of your stuff in an additional private Map; then using the original Map#get/set would just be doing some nonsense work.)

@ljharb @isiahmeadows Somebody has already created SameValueSet, a set that is backed by an ECMAScript Set but effectively uses SameValue for discrimination instead of SameValueZero. It's order-preserving and everything. The same can be done for Maps.

I don't see Map.prototype.set.call(customMap, ...) as a valid use since they're different things. It's as nonsensical as RegExp.prototype.test.call(customMap, ...).

@michaelficarra This is effectively what I was saying, just better put. 👍

If it’s a Map subclass, then borrowing the base method and .calling on it is an idomatic robustness technique; your library extends Set, so it’s entirely sensible to borrow a Set method to .call on it - and these would fail on your library. It’s great that you wrote this, but the caveats are exactly why SameValueZero was the wrong choice for Map and Set.

I would prefer to never again use SameValueZero in the language, wherever consistency/precedent permits.

@ljharb You're right, if it extends Set/Map, you should be able to use the base method on the children. SameValueSet extending Set was a leftover part from an earlier design. I will change it to extend Object instead.

I would prefer to never again use SameValueZero in the language, wherever consistency/precedent permits.

Agreed, though I expect you'll see the consistency argument used in a way you don't think is appropriate.

The updated proposal (#174) uses SameValueZero semantics. However, whether it uses SameValueZero or SameValue remains an open question, to resolve prior to stage 3.

The updated proposal (#174) uses SameValueZero semantics. However, whether it uses SameValueZero or SameValue remains an open question, to resolve prior to stage 3.

In the updated proposal I see that - is treated special as a part of literals and -Infinity. I dunno if it would be too confusing or not but it could be the case that -0 is explictly written as such, it only matches -0.

i.e.

const kind = (v) => match (v) {
  when (-0) { "minus-zero" }
  when (0) { "zero" }
  else { "other" }
}

kind(-0); // minus-zero
kind(0); // zero

One could even have three-typed -0, +0 and 0, the first two of which are strict, and 0 which is simply either.

Alternatively we could have a couple of builtin methods/patterns of Number that check for minus/positive zero, then we could pin them:

Number.isNegativeZero = function isNegativeZero(value) {
  return Object.is(value, -0);
}

Number.isNegativeZero[Symbol.matcher] = function(value) {
  return { matched: Number.isNegativeZero(value), value };
}

Number.isPositiveZero = function isPositiveZero(value) {
  return Object.is(value, +0);
}

Number.isPositiveZero[Symbol.matcher] = function(value) {
  return { matched: Number.isPositiveZero(value), value };
}

const kind = (v) => match (v) {
  when (^Number.isPositiveZero) { "positive-zero" }
  when (^Number.isNegativeZero) { "negative-zero" }
  when (0) { "any-zero" } // Can't actually be reached as previous two functions cover all zeros
}

I agree that if we keep SameValueZero, that it doesn’t make as much sense to allow a signed zero as a pattern - but i still hope for it to use SameValue.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

JLHwung picture JLHwung  Â·  6Comments

j-f1 picture j-f1  Â·  3Comments

tabatkins picture tabatkins  Â·  4Comments

skfreddo picture skfreddo  Â·  6Comments

noppa picture noppa  Â·  3Comments