tc39 / proposal-extended-numeric-literals

Extensible numeric literals for JavaScript
https://tc39.github.io/proposal-extended-numeric-literals/
72 stars 18 forks source link

Potential alternative: Special lexical declarations for literal syntax #26

Open littledan opened 4 years ago

littledan commented 4 years ago

What if we used syntax like this for extended numeric literals?

import { BigDecimal, bigDecimalLiteral } from "./bigdecimal.mjs";
with literal 0d from bigDecimalLiteral;

.1d + .2d === .3d;

Based on the implementation:

// bigdecimal.mjs
class BigDecimal { /* ... */ }

let literalCache = new WeakMap();
export function bigDecimalLiteral(obj) {
  // obj is of the form Object.freeze({string: "123", number: 123})
  if (literalCache.has(obj)) { return literalCache.get(obj); }
  let decimal = new BigDecimal(obj.string);
  literalCache.set(obj, decimal);
  return decimal;
}

Many people (most recently, @erights) have independently given the feedback that it is pretty unfortunate to have to use an extra symbol, whether it's 3@d or 3_d or 3'd, for extended numeric literals. Wouldn't it be great if we could just use 3d, as shown above?

This would be basically like the pre-decorators semantics, but with the twist that, rather than requiring the names start with _, we'd put literal suffixes in a separate namespace! What do you think?

Using a separate namespace might allow us to shadow built-in suffixes (0n) while preserving static analyzability: it's important to be able to understand that 123n is a BigInt statically, if the n suffix is not being shadowed. Some lexical scopes are quite dynamic (e.g., the global object), so if 0n involved an ordinary lexical lookup, and fallback back to the built-in one, it wouldn't be very statically analyzable. By contrast, if we put these in a separate namespace, which is only manipulated by more predictable statements, this regularity would be preserved.

I've heard that, in ES4 times, there was a "no namespaces" resolution. I don't really know what that implies (cc @waldemarhorwat @BrendanEich); apparently private names were acceptable with respect to this goal.

The idea here would be that the with keyword would, in general, mean, "something is being added to the lexical scope". This unifies the with operators from statement proposed in the operator overloading proposal as well as the legacy, deprecated with () { } statement to add an object to lexical scope. Before the with literal statement is executed, that literal would be "in TDZ" if used.

Thoughts?

ljharb commented 4 years ago

How does with literal 0d convey all the subtleties of how to match it, or is it very explicitly with literal 0<letter>? Can i supply any number? Does this mean BigDecimal is an object, not a primitive, since i assume we’re not providing a facility for users to create their own primitives? How would operators work with a non-primitive BigDecimal (and with any user-provided non-primitive type)?

littledan commented 4 years ago

How does with literal 0d convey all the subtleties of how to match it, or is it very explicitly with literal 0?

I was thinking that the syntax would be with literal 0<Identifier> from <Expression>.

Can i supply any number?

The number wouldn't be used, so my intuition is to require it to be 0 to avoid meaningless variation.

Does this mean BigDecimal is an object, not a primitive, since i assume we’re not providing a facility for users to create their own primitives?

No, this is just an example, with an object-based BigDecimal replacement. Probably this was a confusing example... This doesn't have any implications on the syntax or semantics I'm proposing.

How would operators work with a non-primitive BigDecimal (and with any user-provided non-primitive type)?

That'd be provided by the operator overloading proposal. Again, probably this made the example gratuitiously confusing. Extended literals could be used with or without operator overloading, and the function could return either primitives or objects.

ljharb commented 4 years ago

So this would only apply to numbers? How would i do a BigHexadecimal literal, for example? (I’d envision something like 0hDEADBEEF, for example)

littledan commented 4 years ago

Of course the idea would be to support different things like 0x123d. But... we're back to lexical ambiguity for cases like you mention. I had forgotten about that.

littledan commented 4 years ago

OK, thinking about this problem just a little bit: one simple solution would be to prohibit just identifiers starting with [_a-fA-F] as literal suffixes, and also refrain from using them in things like BigDecimal (so that BigDecimal remains polyfillable). Thoughts?

tabatkins commented 4 years ago

This is clearly prejudiced against my base-36 literals proposal.

erights commented 4 years ago

This is clearly prejudiced against my base-36 literals proposal.

No smiley face, so I'm not sure if it is satire. Just in case, could you post a link to the proposal? Thanks.

tabatkins commented 4 years ago

It was satire, apologies. ^_^

hax commented 4 years ago

Even without base-36 literals, it may still have issue in 0x1cm?

littledan commented 4 years ago

@hax This would have to be excluded per the grammar, as I noted in https://github.com/tc39/proposal-extended-numeric-literals/issues/26#issuecomment-571696225

hax commented 4 years ago

@littledan Such prohibition only solve the confusion from machine, can not solve the confusion from people. I feel it will bring more problems than it could solve. For example, people will ask:

littledan commented 4 years ago

Yes, these are some pretty serious concerns that advocates of sigil-less extended numerical literals will have to contend with. cc @BrendanEich @erights

littledan commented 4 years ago

I've landed a version of this proposal, and plan to present it at the July 2020 TC39 meeting.

ckknight commented 4 years ago

One of my concerns with this is that I would need to import on one line and declare it as a literal on another. I'd expect to often have modules whose sole responsibility is to provide numeric suffixes.

littledan commented 4 years ago

@ckknight Yes, that is indeed part of this proposal. An alternative would be to have special export and import forms to save that one line. This would add some complexity, and also be possible to do in a follow-on proposal. What do you think--should it be part of the initial proposal?

import { suffix m } from "./decimal.mjs"

3m
// decimal.mjs
export suffix m = /* ... */;
devsnek commented 4 years ago

I think combined with the existence of numeric separators, 10_cm (or whatever symbol, the point being people are warmed up to the idea of not numbers in their numbers) where cm is a normal value with normal scope semantics is far preferable to a bunch of new syntax and scoping and whatnot.