Propose simpler string constant

# Kip Obenauf (9 years ago)

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'.

# Jordan Harband (9 years ago)

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';.

# Brendan Eich (9 years ago)

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.

# Andrea Giammarchi (9 years ago)

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.

# Alexander Jones (9 years ago)

TypeScript has this already, so I'm sure we can learn a thing or two from their implementation.

# Jordan Harband (9 years ago)

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.

# kdex (9 years ago)

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"?

# Andrea Giammarchi (9 years ago)

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.

# Thomas (9 years ago)

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

# Coroutines (9 years ago)

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 :-)

# Thomas (9 years ago)

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.

# kdex (9 years ago)

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 enums share common value identifiers; so Symbol() really is the only reasonable thing to desugar to.

# Alican Çubukçuoğlu (9 years ago)

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/*, ...*/};
}
# Brendan Eich (9 years ago)

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.

# Ron Buckton (9 years ago)

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 is enum.
  • 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

# Alexander Jones (9 years ago)

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

# Isiah Meadows (9 years ago)

@Alex Those languages have this thing called static types, which enable many of those optimizations.

# Rick Waldron (9 years ago)

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.

# Rick Waldron (9 years ago)

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.

# Steve Kinney (9 years ago)

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.

# Rick Waldron (9 years ago)

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

# Andrea Giammarchi (9 years ago)

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

# Coroutines (9 years ago)

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

# Bob Myers (9 years ago)

One could do worse than look at Python's enums: docs.python.org/3/library/enum.html.

-- Bob

# miguel de Sousa (9 years ago)

Thanks for popping in Brendan!

# Alexander Jones (9 years ago)

What optimizations?

# Brendan Eich (9 years ago)

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.