Propose simpler string constant
That seems hazardous - if someone is converting a "var" codebase to "const"
and "let", and they convert var foo;
to const foo;
expecting it to be
undefined, the current TDZ error will be much more helpful to them than a
silent change of meaning in their code to const foo = 'foo';
.
What's wanted is enum { APPLE, ORANGE, GRAPE }
instead.
Turns out enum
was one of four Java-ish future reserved words that IE
actually reserved from the ancient days. So we could use it. Some
half-formed talk of using it to define categorical sum types, and even
value types -- i.e., allowing constructors for the parts of sum, and other
operations a la methods.
Jordan AFAIK you can't have undefined const declaration so your concern is unfounded.
However, I'm pretty sure what Brendan says is indeed what developers want so I'd +1 that without problems.
TypeScript has this already, so I'm sure we can learn a thing or two from their implementation.
Correct, when const foo;
throws, my concern doesn't exist. The concern I
was talking about is if this initial suggestion went through, and const foo;
would become const foo = 'foo';
, that a new refactoring hazard
would be created - which is the entire reason const foo;
throws as-is.
Agreed that an enum
construct would be very useful.
Honestly,
const ADD_ALL_THE_STUFF = "ADD_ALL_THE_STUFF";
doesn't seem like a intended approach anyway, even if there was a
shorter syntax. Symbol()
seems to be the better choice if the
objective is to manage references whose values indicates special
semantics; I have yet to see a use case where a string turns out to be
beneficial. Semantically, what should
typeof (enum {
APPLE,
ORANGE,
GRAPE
}).APPLE;
evaluate to? A "symbol"
called with its identifier, an
identifier-valued "string"
, an integer-valued "number"
?
FWIW I think If an enum should hold a unique value either "symbol" or a new "enum" type, otherwise I think "number" would be easier way to go.
IMHO it'd be a huge mistake to not use symbols for enums.
In my head this:
const colours = enum { Red, Yellow, Green, Blue }
should 'desugar' to something like this in ES6:
const colours = { Red: Symbol('Red'), Yellow: Symbol('Yellow'), Green: Symbol('Green'), Blue: Symbol('Blue') }
Thomas
On Wed, Dec 16, 2015 at 3:20 AM, Thomas <thomasjamesfoster at bigpond.com> wrote:
IMHO it'd be a huge mistake to not use symbols for enums.
In my head this:
const colours = enum { Red, Yellow, Green, Blue }
should 'desugar' to something like this in ES6:
const colours = { Red: Symbol('Red'), Yellow: Symbol('Yellow'), Green: Symbol('Green'), Blue: Symbol('Blue') }
Thomas
This person gets it. +1 :-)
Out of curiosity, is anyone working on a proposal for enum? Given the keyword has been reserved for so long, it seems a little strange for a proposal not to be floating around - unless I've missed something.
Yes, I'd favor the Symbol()
approach a lot. @Andrea: Alternatively
desugaring enum
values to Number
is probably an approach that leads
back to C/C++'s #define
(which turns out to be a disastrous idea, at
least software-engineering-wise), since this has horrendous impacts on
comparison. Consider this:
const PIXELS = enum {
RED,
GREEN,
BLUE
};
const VEGGIES = enum {
ONION,
TOMATO
};
assert(PIXELS.RED === 0);
assert(VEGGIES.ONION === 0);
// Oops
assert(PIXELS.RED === VEGGIES.ONION);
Alternatively choosing String()
would suffer from the same issue, but
only clash when two enum
s share common value identifiers; so
Symbol()
really is the only reasonable thing to desugar to.
How are enums declared?
let myVar = 13;
enum myEnum = {Red, Green, Blue};
// How does it work with classes?
class MyClass {
static myVar = 13; // "Class Fields & Static Properties" which omits "let"
static enum myEnum = {Red, Green, Blue}; // Now we decided to use "enum"
while we omitted "let" before
// Maybe "Class Fields & Static Properties" should use "let" and "const"?
}
Or:
let myVar = 13;
let myEnum = enum {Red, Green, Blue};
// How does it work with classes?
class MyClass {
static myVar = 13; // "Class Fields & Static Properties" which omits "let"
static myEnum = enum {Red, Green, Blue}; // All fine?
}
Do we allow this:
let myEnum = enum {Red, Green, Blue};
myEnum.Red; // 0 or Symbol("Red")
myEnum[myEnum.Red]; // Can we get "Red" like this?
If we do, how does this work?
let myEnum = enum {Red, Green, Blue};
myEnum.Alpha = 3;
myEnum[myEnum.Alpha]; // Who is going to put "Alpha" here? It magically
happens? Probably not.
Take HTMLMediaElement
for an example, which has these:
- HTMLMediaElement.HAVE_NOTHING = 0
- HTMLMediaElement.HAVE_METADATA = 1
- HTMLMediaElement.HAVE_CURRENT_DATA = 2
- HTMLMediaElement.HAVE_FUTURE_DATA = 3
- HTMLMediaElement.HAVE_ENOUGH_DATA = 4
And they can be used like this:
if (myVideo.readyState >= HTMLMediaElement.HAVE_METADATA) {
goodToGo();
} else {
waitForIt();
}
How are we going to achieve the same functionality with "Symbol Enums"?
Don't use Symbols?
Make Symbols comparable or use something special like NumberSymbol which is
comparable like this?
Don't allow users to do this and have them implement methods like
isReadyStateGreaterThan()
or readyStateToNumber()
?
???
AFAIK, enums can do this in other languages:
let myEnum = enum {Red = 2, Green, Blue};
// Green is 3, Blue is 4.
Maybe we can give the user to pick one:
let myEnum = enum {Red, Green, Blue}; // Symbol Enum
let myEnum = enum {Red = 0, Green, Blue}; // Number Enum
Looks confusing.
How will a user be able to achieve an HTMLMediaElement
like result?
(Making enum'd values properites of the class)
class HTMLMediaElement extends HTMLElement {
// Like this?
static enum {HAVE_NOTHING, HAVE_METADATA/*, ...*/}
// A "Number Enum" generates an object so use it with something crazy
like "Class Spread Properties"?
static ...enum {HAVE_NOTHING, HAVE_METADATA/*, ...*/} // What?
// Maybe not supporting anything like above, and forcing users to put
enums in a property
static readyStates = enum {HAVE_NOTHING, HAVE_METADATA/*, ...*/};
}
On Wed, Dec 16, 2015 at 9:41 AM Alican Çubukçuoğlu < alicancubukcuoglu at gmail.com> wrote:
How are enums declared?
let myVar = 13; enum myEnum = {Red, Green, Blue};
No =
between name and {
.
Enumerator scope is a good question. Clearly we do not want global scope. Rather, as a declaration immedicately contained by a block or top level, we want lexical scope for the enum name -- and (I think) for the enumerators' individual names.
What about enumerator name scope for enum in class, without static
? I'm
not sure, but precedent says that the enumerator names define prototype
properties.
Expression as well as declaration enum
form follows class and function
precedent. Expression form requires a reserved identifier (not sym or sum
or whatever), which enum
has been forever, fortunately.
I agree symbol values by default, with = 0
or some other number than 0
after the first enumerator name, looks confusing. Recall the first use-case
in the o.p. was (implicit, should rather be explicit) reflection on the
string value that spells the enumerator name.
In C#, an enum is a declaration that defines a distinct type consisting of a set of named constant values of that type. The distinct type of the enum has an underlying numeric base type, such as byte, int, long, etc. By default, each constant value is assigned an incrementing integer value starting from 0, although this sequence can be interrupted by assigning an explicit integer value:
enum Colors { Red, Green, Blue }
Colors.Red // 0
Colors.Green // 1
Colors.Blue // 2
enum Days: byte { Sat = 1, Sun, Mon, Tue, Wed, Thu, Fri }
Days.Sat // 1
Days.Sun // 2
C# enum values can be used with many standard numeric operations, and are often heavily used with bitwise operators. A C# enum value is a constant value, and is internally treated as a numeric literal by the compiler. In any C# enum declaration, only constant numeric values can be explicitly assigned to an enum member, although constant reduction is permitted:
enum UriComponents {
Scheme = 0x1,
UserInfo = 0x2,
Host = 0x4,
Port = 0x8,
Path = 0x10,
Query = 0x20,
Fragment = 0x40,
AbsoluteUri = Scheme | UserInfo | Host | Port | Path | Query | Fragment
}
Although C# enum values are converted to numeric literals by the compiler, their type information is preserved. This allows for an enum type to have different behavior from a literal numeric type. One example of this behavior is how ToString is handled on an enum value:
enum Numbers { Zero, One, Two }
(int)Numers.Zero // 0
Numbers.Zero.ToString() // Zero
The enum type itself has a number of static methods to make it easier to program against, including: GetName, GetNames, GetUnderlyingType, IsDefined, Parse, and ToObject. Instance members of an enum type include HasFlag and CompareTo.
In TypeScript we treat an enum declaration in a fashion similar to a C# enum, with respect to how we handle incremental integer values and explicitly assigned values. We effectively emit an enum as an object literal:
// TypeScript
enum Colors { Red, Green, Blue }
Colors.Red // 0
// JavaScript
var Colors;
(function (Colors) {
Colors[Colors[0] = "Red"] = 0;
Colors[Colors[1] = "Green"] = 1;
Colors[Colors[2] = "Blue"] = 2;
})(Colors || (Colors = {}));
Colors.Red // 0
In this way, you can use Colors.Red
to get the value 0, and Colors[0]
to get the value "Red". As a performance optimization we also have what we call "const enums". A const enum can be completely erased by the compiler:
// TypeScript
const enum Colors { Red, Green, Blue }
Colors.Red // 0
// JavaScript
0 /*Colors.Red*/ // 0
I think a general proposal for ES enums would be a combination of the above approaches, with some additions:
- An enum can be a declaration or an expression.
- The body of an enum consists of a new lexical scope.
- Enum members are standard JavaScript identifiers.
- Enum members are automatically assigned an incrementing integer value, starting at zero.
- Enum members can be explicitly assigned to an integer value, or another enum value.
- Within the body of an enum, Enum members can be accessed in the initializer without qualification.
- Within the body of an enum, Enum members are lexically declared names and cannot be accessed before they are defined (TDZ).
- An enum declaration can be called as a function to convert a string or numeric value into the enum value, making enum types distinct from numbers and from each other. [1]
- The result of
typeof
on an enum value isenum
. - Enum values support (at least) the following operators, returning an enum value of the same type: + (unary), - (unary), ~, + (binary), - (binary), | (binary), & (binary), ^ (binary).
- Any binary operation between two enums of different types is a TypeError. [1]
- Any binary operation between an enum and a number first converts the number to the enum type. If the number is not an integer it is a TypeError.
- Any binary operation between an enum and a string first converts the enum into the string value for that enum based on the enum member's JavaScript identifier (if present), or the string representation of its integer numeric value. [2]
- Calling Number() with an enum value as its first argument returns its underlying number value.
- Calling String() with an enum value as its first argument returns the string value for the enum member that defines the number (if present), or the string representation of its integer numeric value. [2]
- Calling the valueOf() instance method on an enum value has the same effect as Number() above.
- Calling the toString() instance method on an enum value has the same effect as String() above. [2]
- Two enum members on the same enum or differing enum types with the same underlying integer value are equivalent (==) but not strictly/reference equivalent (===). [1]
I think these rules could satisfy both anyone who needs enum values to be numeric (to support bitwise operations, bitmasks, ordinal indices, etc.) and those that would like enum values to be unique in a fashion similar to using Symbol().
[1] We have noticed in TypeScript some issues with Symbol-like equivalence with enums if you have two versions of the same module in NodeJS due to specific version dependencies, where you could have a.Color.Red !== b.Color.Red if a and b are different versions of the same module. Generally I think having enum values just really be numbers and not differing between == and === is less of a footgun.
[2] If enum values of different types should be === to each other, you should not be able to get a different result when you call .ToString(). In that case, we could add a static getName
method to the enum type to get the string value for an enum.
Ron
I think we can do better than to follow C#'s lead, if that's the case. I
don't see what the point in Color.Red - Color.Green
is. Languages like
Rust, Swift, OCaml are providing full Algebraic Data Types that approach
this whole domain from a better level. Also, I don't like the idea of
overloading enum for both sets of combineable flags and sets of distinct
values - that seems like a lack of a good abstraction. Look to Qt for IMO a
pretty good one: doc.qt.io/qt-4.8/qflags.html
Alex
@Alex Those languages have this thing called static types, which enable many of those optimizations.
On Wed, Dec 16, 2015 at 6:20 AM Thomas <thomasjamesfoster at bigpond.com>
wrote:
IMHO it'd be a huge mistake to not use symbols for enums.
In my head this:
const colours = enum { Red, Yellow, Green, Blue }
should 'desugar' to something like this in ES6:
const colours = { Red: Symbol('Red'), Yellow: Symbol('Yellow'), Green: Symbol('Green'), Blue: Symbol('Blue') }
When Brendan mentioned enum earlier in this thread I started writing a proposal, which has evolved into exactly this. I should have something worth reading by next week.
On Wed, Dec 16, 2015 at 7:27 AM Thomas <thomasjamesfoster at bigpond.com>
wrote:
Out of curiosity, is anyone working on a proposal for enum? Given the keyword has been reserved for so long, it seems a little strange for a proposal not to be floating around - unless I've missed something.
Yes. It's an early draft, but I will get it onto github as soon as possible.
I did some initial thinking about this and looked at Rust and Swift as prior art. I started something similar to what was being discussed but came across some edge cases as I went along.
stevekinney/ecmascript-enumerations
Right now, it's just a draft of notes and thoughts, but I am working on a draft implementation with Sweet.js.
On Thu, Dec 17, 2015 at 3:33 PM Steve Kinney <hello at stevekinney.net> wrote:
I did some initial thinking about this and looked at Rust and Swift as prior art. I started something similar to what was being discussed but came across some edge cases as I went along.
stevekinney/ecmascript-enumerations
Right now, it's just a draft of notes and thoughts, but I am working on a draft implementation with Sweet.js.
I had originally had values with implicit ordinal numbers, but Mike Pennisi (co-author) and I agreed that with Symbol there isn't much benefit to making the raw value a number.
Anyway, this is the WIP rwaldron.github.io/enum-definitions
Since I had no answer in the other thread, I'll try here too ( and then drop every hope somebody will answer :-) )
Copy-pasting myself:
... is interoperability between PL and different environments a concern at all?
I think having unique identifiers like Symbols is a better solution too but right yesterday I've made a PR to convert V8 Enum type to GObject enums using its integer value [1] 'cause that's how it also work the other way round [2].
How would a JS Symbol as enumerable be represented in Emscripten generated code or how could Int32 enum based PL transpile their enums to JS?
Maybe I am overlooking at this and it's not a real issue?
[1] WebReflection/gnode/commit/56c5801866452c1e4973ed0c42f80dcda9d3d8c6 [2] WebReflection/gnode/blob/master/src/value.cc#L279
On Thu, Dec 17, 2015 at 1:42 PM, Rick Waldron <waldron.rick at gmail.com> wrote:
Anyway, this is the WIP rwaldron.github.io/enum-definitions
I like how this looks.
If a value is not assigned to one of the enumerated identifiers, it's assumed that you're defining all symbols (as you're not hinting at an order or using a pooled (in memory) value).
What I had thought about was this:
enum { A B C } # A, B, and C become const Symbol references (Symbol('A'), Symbol('B'), Symbol('C'))
enum { A = 0, B, C } # A, B, and C become const number references (0, 1, 2)
enum { A, B = 'bat', C } # A, B, C become const string references ('A', 'bat', 'C')
enum { A, B = 2, C = 'cat' } # becomes: const A = 'A'; const B = 2; const C = 'cat';
enum whatever { ... }; # would just become: var whatever = { ... } of course
My reasoning is this:
By assigning a value to any of the identifiers within an enum, you are not using the identifier as reference to a unique value (which means you don't want what Symbol has to offer).
Basically if you attach a value to anything within an enum, the entire enum DOES NOT define any Symbols.
If it's a number value and there are no other non-number values in the enum: we order it like a C enum.
In a non-number or mixed-value enum, the identifiers default to becoming the strings they reference: enum { CAT = 3, DOG = 'whatever', FISH } # becomes: const CAT = 3; const DOG = 'whatever'; const FISH = 'FISH';
PS: disregard my lack of let/var
One could do worse than look at Python's enums: docs.python.org/3/library/enum.html.
-- Bob
Thanks for popping in Brendan!
What optimizations?
thanks to you and Mike for drafting. I will read and comment more when I have time, but I wanted to channel the Twitter-ghost of Dave Herman:
"we should start from pattern matching and work our way from there into the kinds of datatypes we want to match on"
I hear wycats and bterlson are working on pattern matching. Ccing them.
A common pattern I see is this: const ADD_ALL_THE_STUFF = 'ADD_ALL_THE_STUFF'
Would it be helpful to allow a shorter version of this to be: const ADD_ALL_THE_STUFF
Rather than have that just be forever undefined, it could automatically refer to a string of the same name, i.e. 'ADD_ALL_THE_STUFF'.