Skip to content
This repository has been archived by the owner on Oct 10, 2019. It is now read-only.

Should unary + be made goofy because of asm.js? #25

Closed
syg opened this issue Mar 21, 2017 · 19 comments
Closed

Should unary + be made goofy because of asm.js? #25

syg opened this issue Mar 21, 2017 · 19 comments

Comments

@syg
Copy link

syg commented Mar 21, 2017

It is my understanding that, at Mozilla at least, we'd like to sunset asm.js opts sooner rather than later. Integers, by all means, should outlive asm.js by a long, long time. When do we optimistically estimate that Integer will ship unflagged in the various engines? That might be distance enough in the future that we need not, and should not, make unary + goofy.

@littledan
Copy link
Member

Well, it'd still be nice if shipping Integer didn't block on removing asm.js. Different browsers have different appetites for these deprecations, and some might want to keep it for longer. But anyway, I agree, where possible, better to not carry technical debt.

I see a couple options to potentially decouple the two, under the assumption that asm.js will be removed and Integer will not be added to it, but to allow the two to temporarily coexist in some engines:

  • Check for Integer arguments when calling into an asm.js program from the outside, and run the normal interpreted path if an argument is an Integer.
  • Initially ship Integer with unary + throwing on Integer, then upgrade to return the identity when asm.js is removed (normative optional anybody?)

Any opinions on these?

@Jamesernator
Copy link

I don't think preserving the identities for asm.js makes sense really if asm.js is ultimately going to be removed by browsers so I'd prefer the former option simply because when asm.js is removed any code still using asm.js is going to be run as the former anyway.

@littledan
Copy link
Member

There's another argument, that non-asm code may rely on the invariant that + returns a number. Unlike unary -, which does a small calculation, the only utility from + is converting its input to a Number.

@ljharb
Copy link
Member

ljharb commented Mar 28, 2017

That's a big utility; it's a clear, safe, syntactic way to coerce to a number.

@syg
Copy link
Author

syg commented Mar 28, 2017

The non-asm.js argument for + is more compelling to me. OTOH, if there is existing non-asm.js code that depends on + casting to a Number specifically that would break, I would be inclined to think there also exists non-asm.js code that depends on - casting to a Number specifically and then negating it.

@ljharb Do you think the utility is + specifically coercing to a Number with double semantics, or to a numeric value?

@ljharb
Copy link
Member

ljharb commented Mar 28, 2017

To a numeric value; specifically so I can pass it to functions expecting a numeric value and call prototype methods on it.

@syg
Copy link
Author

syg commented Mar 28, 2017

@ljharb The distinction here is that + is currently proposed to keep its current semantics of only returning a Number for asm.js and existing code that depend on that, and not apply to the new Integer type.

+ definitely retains its convert-to-numeric utility if it also applied to Integer types. So the question is are there compelling examples of existing non-asm.js code meaningfully breaking.

@ljharb
Copy link
Member

ljharb commented Mar 28, 2017

Since typeof 0n is not "number", and Integer.prototype will not have all of (or even many of) the methods on Number.prototype, I can imagine some breakage if +0n produces something that's not typeof "number".

I assume +x would need to work the same as Number(x) - and I'm not sure from a user intuition standpoint why an Integer wouldn't be trivially coercible to a Number - so it would make no sense to me for +0n to throw.

Separately, the value of having a syntactic, safe method of always producing a value of typeof "number" shouldn't be understated; and I'd want a similar syntactic method of always producing a value of typeof "integer". If + isn't that, could there be something else?

@littledan
Copy link
Member

@syg I'd hypothesize that most code which uses unary + intends to cast arbitrary values to Numbers, whereas most code which uses unary - intends to negate something that's already a Number. But that's not based on any evidence--not sure how to go about collecting it.

@ljharb
Copy link
Member

ljharb commented Mar 28, 2017

There is definitely code using both unary and binary minus to cast to a number; I'm pretty sure some of my shims do that.

@Jamesernator
Copy link

Jamesernator commented Mar 28, 2017

The thing is though, wrong arguments passed to a function is wrong arguments anyway, even if a function uses + to coerce to a number it doesn't change the fact that the wrong types were passed into the function to begin with, should this really be a concern of Integer?

Like say I have a function

function foo(arg) {
    const x = +arg
    return [1,2,3,4][x]
}

Does it really matter if it behaves unexpectedly in the case of, given that Integers aren't really a valid argument to begin with?

foo(2n) // undefined as [1,2,3,4]['2n'] would be undefined

Because as it stands no existing code will break from this behaviour, and if an Integer slips into the code that's simply a concern of either

  • testing
  • static typing (e.g. flow)
  • simply writing code that doesn't pass wrong types to functions
  • or any combination of the above

@ljharb
Copy link
Member

ljharb commented Mar 29, 2017

@Jamesernator since few things throw when attempted to be coerced to a number, and Integers have a very intuitive implicit conversion to Number, why would we want it to throw?

I would also expect that the spec itself would want to start returning Integers instead of Numbers from builtin methods - if there was an implicit coercion from Integer to Number, it might be web compatible to make .length or .size return an integer, for example.

@Jamesernator
Copy link

Jamesernator commented Mar 29, 2017

@ljharb I'm not suggesting that +Integer throws but rather that it simply acts as identity for integers, e.g. +1n === 1n and performs no type coercion whatsoever.

The thing is I see only drawbacks to coercing it to a number e.g. this simple case:

function add(a, b) {
    return (+a) + (+b)
}

// Supposing a hypothetical syntax for big exponents
add(1e100n, 1e100n)

Would be Infinity, which means existing functions which otherwise would be logically sound with no coercion couldn't be used at all with the Integer type. I'd much rather that throw than coerce to number and certainly much rather it simply equal 2e100n.

Also returning integers from .length on existing types anyway will almost certainly never be web-compatible simply for the reason that checking if typeof <obj>.length is number is currently is ArrayLike even the top result from google uses it so I'm certain there'll be a non-negibible amount of code using that heuristic.

Of course I have no issues with future methods returning Integers or even future .length returning Integers, just that I don't see a compelling case + coercing to a Number or how future .length/.size would be a compelling reason for it to do so.

@ljharb
Copy link
Member

ljharb commented Mar 29, 2017

Regardless of what +x does (which is currently the same thing as what Number(x) does), how would I go about explicitly converting an Integer to a Number? That conversion must be possible somehow.

@Jamesernator
Copy link

Jamesernator commented Mar 29, 2017

The spec specifies that calling Number(x) would be sufficient.

What I want to see is +x not be tied to Number(x) but rather that the definition of + is changed such that + will instead simply work as identity for future numeric types, while maintaining the [toPrimitive]('number') legacy behaviour in cases where + can't be resolved for the type, with perhaps additional additions to the algorithm if operator overloading is ever added proper but I wouldn't suggest that a [Symbol.operators.unaryPlus]() {} or anything like that be added to the Integer proposal as it's beyond the scope of simply adding an Integer type and there's no reason such a thing can't be added later anyway.

@littledan
Copy link
Member

@Jamesernator What do you mean by "can't be resolved for the type"?

@Jamesernator
Copy link

Jamesernator commented May 2, 2017

@littledan Basically I mean + would be redefined to instead of calling ToNumber it would call some internal field (say [[UnaryPlus]] or something like that) and that this field would be defined on Integer to simply return the integer back. When I said can't be resolved for the type I just meant that if this field is not defined then fall back to using ToNumber.

Effectively in spec text terms I'd like to see Unary + replaced with something along these general lines (there's probably better ways to define it):

UnaryExpression: +UnaryExpression
    1. Let expr be the result of evaluating UnaryExpression.
    2. if Type(expr) has a [[UnaryPlus]] internal slot then
        a. return Type(expr).[[UnaryPlus]](expr)
    3. Return ? ToNumber(? GetValue(expr)).

With BigInt's defining an internal slot:

BigInt.[[UnaryPlus]](value)
    1. return value

@littledan
Copy link
Member

@Jamesernator I don't think any of that mechanism is necessary--we already have ToNumeric. I think we'd just make + do ToNumeric. The question @syg is raising in this bug is, should we be doing ToNumber (as currently), or ToNumeric? ToNumeric is the more natural choice, but ToNumber was motivated here by a certain kind of compatibility which might not be relevant by the time BigInt would ship.

@littledan
Copy link
Member

We discussed this question at the May 2017 TC39 meeting and decided to stick with throwing on +BigInt.

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

No branches or pull requests

4 participants