Adding support for enums

# Doug Wade (5 months ago)

Hello friends!

I had a bug the other day on my team. We use redux redux.js.org to

manage the state on our application resumes.indeed.com, which is

maintained by a large team. In Redux, there are actions, which are strings, that get passed to a reducer, which mutates the states. My coworker and I inadvertently added the same action name, "LOADING" on the same page, but in two different files. This led to a bug when both my modal and his search results would be loading at the same time, but since they were never both visible, we didn't catch the bug. My coworker refactored his feature, and broke my feature, such that rather than displaying a spinner, we went straight to an empty results page, even when there were results.

In other languages, like the language I use most at work, Java, we would instead use a language construct called an enum en.wikipedia.org/wiki/Enumerated_type in this situation so that

the two different sets of actions weren't equal to each other. I did some research into some previous discussions on this esdiscuss.org/topic/enums topic, and it seems like the discussion

has been broadly in favor of it. I also noted that enum is a reserved keyword developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords,

which indicates some intention to add enums to the language.

As such, I've spent some time working on a proposal doug-wade/proposal-enum-definitions for adding enums

to ECMAScript. It is very heavily based on the work by rauschma rauschma/enums/blob/master/enums.js, stevekinney stevekinney/ecmascript-enumerations and rwaldron rwaldron/proposal-enum-definitions. I wasn't sure if I

was using all the right words when writing the proposal, so to help express myself better, I also spent some time writing a babel plugin doug-wade/babel/tree/babel-plugin-proposal-enum that

uses a polyfill doug-wade/enum-polyfill against which

I've written a small test suite doug-wade/enum-unit-tests (if you would like to run

them, you'll need to link the polyfill and the babel plugin into the tests). Please do not take these as any indication of "done-ness", I wrote them to understand how I would expect an enum in javascript to behave, and am willing and eager to make changes as I get suggestions. I do, however, feel I have done as much as I can on my own, and would like help in considering the proposal, especially whether it contains any footguns, undefined behavior, or things that would be surprising to newer developers, and helping me identify what work is to be done to make this a "real" proposal.

All the best, Doug Wade

# Isiah Meadows (5 months ago)

I normally do one of two things:

  1. Do Object.freeze({...}), where the object keys point to the values in question.
  2. Define the variants as constants with a descriptive prefix.

Both of these forms could in theory be inlined by engines (no clue why they still opt to load them). But as it stands, I basically learned how to work around the lack of enums in a way effective enough I lost the need for them.

You could use numbers (what I usually use - my enums don't typically mix with other enums), strings, or symbols (best for you). There's pros and cons to each:

  • Numbers are the most efficient of all of these, they are serializable, and you can retain order. However, adding variants is a bit harder, and debugging is a bit harder.
  • Strings are the simplest, the easiest to debug, and are serializable, but they don't retain order, and they are the slowest.
  • Symbols are the most extensible (you don't need to be careful to avoid duplicates), can be intermixed with other symbols, and are also fairly easy to debug (almost on par with strings) if you label them with their names. They don't retain order, and they aren't serializable.

If you really want sugar, you could easily define a function that creates the variants for you, but I eventually stopped doing it because I found it wasn't actually saving me any lines of code without hindering readability.

Also, because each of these have their strengths, I have found myself using them in different scenarios:

  • Numbers are my default. They also let me pack other info alongside them (like flags), which make them more attractive for me.
  • Strings are nice for prototyping. You can readily inspect and modify them freely without even having to change the source code.
  • Symbols are best when you have to worry about types getting otherwise ambiguously mixed together within a single channel. I don't frequently find myself in this situation, but I do on occasion.

Or, in summary, as much as I like the idea of explicit language enums, they don't seem necessary to me anymore in JS at least.

# Bob Myers (5 months ago)

You could wait three years for enums in JS, or you could just start using a statically typed flavor of JS which has had them for years.

# Michael J. Ryan (5 months ago)

Just use symbols for your action type

# kai zhu (5 months ago)

In Redux, there are actions, which are strings, that get passed to a reducer, which mutates the states. My coworker and I inadvertently added the same action name, "LOADING" on the same page, but in two different files. This led to a bug when both my modal and his search results would be loading at the same time, but since they were never both visible, we didn't catch the bug.

can you explain in code how either enum or symbols could solve your problem? reading up on how reducers work @ redux.js.org/basics/reducers#handling-more-actions, redux.js.org/basics/reducers#handling-more-actions, i'm guessing the problematic code looks like the following.

// module spinner.js
var action = {
    type: 'LOADING',
    ...
};

// module search.js
var action = {
    type: 'LOADING',
    ...
};

// module main.js
switch (action.type) {
case 'LOADING':
    // inadverdently run both
    // spinner-loading and
    // search-result-loading actions
    ...
    break;
}

its not obvious to me how enums/symbols could be use in a less-complicated solution, than simply renaming action.type in your case with more descriptive names that won’t collide (e.g. 'LOADING_SPINNER', ‘LOADING_SEARCH_RESULT').

kai zhu kaizhu256 at gmail.com

# Nicolò Ribaudo (5 months ago)

Have you seen rbuckton/proposal-enum? The champion is a TypeScript member, so he already had experienc with enums.

# kai zhu (5 months ago)

reading-up rbuckton/proposal-enum, rbuckton/proposal-enum, i'm guessing this would be the enum-solution to doug's name-collision problem?

/*
 * enum-solution with action = { type: Actions.LOADING_SPINNER, ... }
 */
enum Actions {
    LOADING_SPINNER,
    LOADING_SEARCH_RESULT
}
...
switch (action.type) {
case Actions.LOADING_SPINNER:
    ...
    break;
case Actions.LOADING_SEARCH_RESULT:
    ...
    break;
}

the above may look nice and familiar to backend-java-developers, but for javascript-frontend-developers trying to manage integration-level complexity, it looks like needless extra-overhead when simpler, throwaway glue-code would likely suffice:

/*
 * plain-string solution with action = { type: 'LOADING_SPINNER', … }
 */
switch (action.type) {
case 'LOADING_SPINNER':
    ...
    break;
case 'LOADING_SEARCH_RESULT':
    ...
    break;
}

kai zhu kaizhu256 at gmail.com

# Andrea Giammarchi (5 months ago)

it seems to be trivial enough to implement in user-land though

class Enum {
  constructor(...names) {
    [].concat(...names).forEach(name => {
      this[name] = Symbol(name);
    });
    Object.freeze(this);
  }
}

then you can have:

const Actions = new Enum(
  'LOADING_SPINNER',
  'LOADING_SEARCH_RESULT'
);

or using an Array if you prefer, with eventually the ability to use an object to map keys to values, instead of using symbols.

# Doug Wade (5 months ago)

Thanks Nikolo, that was exactly what I was looking for.

Best,

# Isiah Meadows (5 months ago)

This was the kind of library helper I was alluding to in my email. It's incredibly easy to introduce a zero-overhead library solution to this problem.

To be quite honest, I'm not convinced we actually need enums in JS. I've used design patterns enough that it in my experience is much more flexible than enums, while in the degenerate case of enums, doesn't actually add any lines of code, and it doesn't really decrease boilerplate on the user side. Also, enums aren't common enough in idiomatic JS to really be useful - we can technically use just about anything to discriminate types on, and our dynamic typing avoids most of the issues that make things like sum types and sealed/abstract classes pervasive in other languages.


Isiah Meadows me at isiahmeadows.com, www.isiahmeadows.com

# Peter Hoddie (5 months ago)

I'm not convinced we actually need enums in JS.

I'm not in a position comment on the need for enums on the web. However, JavaScript is used beyond the web. For example, we use JavaScript on embedded devices to implement device drivers and network protocol handlers where having enums in the language would welcome. Without them, we end up with more complex code. Here are a couple examples.

The proposal Ron Buckton presented at TC-39 is nice for simple integer enums. This snippet from our BLE implementation...

let ADType = Object.freeze({
	INCOMPLETE_UUID16_LIST: 0x02,
	COMPLETE_UUID16_LIST: 0x03,
	INCOMPLETE_UUID128_LIST: 0x06,
	COMPLETE_UUID128_LIST: 0x07,
	...
});

..becomes more clear as...

enum ADType {
	INCOMPLETE_UUID16_LIST = 0x02,
	COMPLETE_UUID16_LIST,
	INCOMPLETE_UUID128_LIST = 0x06,
	COMPLETE_UUID128_LIST
	..
}

And bitfields are contemplated, so that this one...

let ADFlag = Object.freeze({
	LE_LIMITED_DISCOVERABLE_MODE: 0x01,
	LE_GENERAL_DISCOVERABLE_MODE: 0x02,
	NO_BR_EDR: 0x04,
	...
});

...becomes...

@Enum.flags
enum ADFlag {
	LE_LIMITED_DISCOVERABLE_MODE = 1
	LE_GENERAL_DISCOVERABLE_MODE,
	NO_BR_EDR,
	...

}

These are welcome improvements to the source code. Whether the overall benefits of enum across the range of situations JavaScript is applied to make it worth adding enums to the language is now open for discussion thanks to Ron's proposal.

# Isiah Meadows (5 months ago)

I'm not a purely front-end developer - I do full stack, and my comfort zone is with Node.js. (My current work is really a giant CRUD app in Rails with some extra data-driven features, so my JS stuff is purely for personal use.) Keep in mind, I use integer enums a lot myself, since I also find myself writing a lot of low-level code nowadays. (When performance becomes critical, you have to track any and every allocation you make, so integer enums are infinitely useful.) I don't do a lot of embedded/IoT work, though, so I can't speak for your experience there.

As for your thing, the auto-counter is nice, although it's easily implemented in userland and when managing bit masks, I find it more useful to track the raw value directly rather than to allow the value to be inferred (it's easier to read and easier to maintain). The rest is basically just trading one set of boilerplate for another, and I'm not convinced of its value. I find the Object.freeze variant perfectly acceptable, but I've lately just started using raw constants like const FlagFoo = ... and similar, since they allow enum dependencies, while remaining fairly static. The only missing piece here is for UglifyJS to start evaluating and inlining constant variables set to literals by default, which would make them equally zero-cost to TypeScript's const enums (which I almost exclusively use in practice).

Isiah Meadows me at isiahmeadows.com, www.isiahmeadows.com

# kai zhu (5 months ago)

peter, appreciate your perspective for embedded-device use-case. however, as @isiah mentions, there are existing javascript design-patterns using dicts/lists that are superior to enums. the primary reason (which was implied) is because they can be easily stored/shared/parsed as json config-settings, e.g.:

{
    "DEVICE_LIST": [
        {
            "TYPE": "ARDUINO",
            "ADFlag": {
                "LE_LIMITED_DISCOVERABLE_MODE": 1,
                "LE_GENERAL_DISCOVERABLE_MODE": 2,
                "LE_GENERAL_DISCOVERABLE_MODE_ARDUINO": 3,
                "NO_BR_EDR": 4
            },
            "ADType": {
                "INCOMPLETE_UUID16_LIST": 2,
                "COMPLETE_UUID16_LIST": 3,
                "INCOMPLETE_UUID128_LIST": 6,
                "COMPLETE_UUID128_LIST": 7
            }
        },
        {
            "TYPE": "RASPBERRY_PI",
            "ADFlag": {
                "LE_LIMITED_DISCOVERABLE_MODE": 1,
                "LE_GENERAL_DISCOVERABLE_MODE": 2,
                "NO_BR_EDR": 4
            },
            "ADType": {
                "INCOMPLETE_UUID16_LIST": 2,
                "COMPLETE_UUID16_LIST": 3,
                "INCOMPLETE_UUID128_LIST": 6,
                "COMPLETE_UUID128_LIST": 7
            }
        }
    ]
}

you can also save/update these configs to a github-repo (with encryption like this real-world example [1]), and perhaps have your iot device poll it every 5 minutes for effortless config-updates in near-realtime.

-kai

[1] encrypted travis-ci-config-settings hosted on github kaizhu256/node-utility2/blob/gh-pages/CRYPTO_AES_SH_ENCRYPTED_kaizhu256

# Peter Hoddie (5 months ago)

Kai --

Thank you for sharing for your thoughts.

there are existing javascript

design-patterns using dicts/lists that are superior to enums. the primary reason (which was implied) is because they can be easily stored/shared/parsed as json config-settings,

You could use JSON, but it is really quite different. Syntax is more verbose, the values may be changed, and it requires runtime parsing. For the places we would use enums, this example of JSON is not superior.

you can also save/update these configs to a github-repo (with encryption like this real-world example [1]), and perhaps have your iot device poll it every 5 minutes for effortless config-updates in near-realtime.

The use of enums I described is for true constants. These are values carved in stone as part of a Standard. The values will never change, certainly not every five minutes. ;)

# Isiah Meadows (5 months ago)

Just wanted to clarify one thing: I did not state they were superior in any explicit sense. I just stated they're equal, which is different. If it were in fact superior, this proposal would've been much more easily dismissed. But them being equal is why I was asking "why have yet another way of expressing the same concept?".

Oh, and also, my particular use case for enum-like constructs involves numeric constants, and when you're dealing with performance-sensitive protocols, you'll 1. need to care about data size (which pushes JSON to its limits), and 2. worry about the message size and deserialization speed of the messages themselves. Peter and I have pretty similar use cases and constraints (not identical), and I can tell you that having highly flexible, easy-to-alter (due to frequently changing requirements or third-party protocols) code is not very high on either of our lists.

In addition, when you're dealing with that kind of code, string comparison is dog slow compared to integers - it's literally faster to concatenate two strings (complete with allocation) than it is to compare two strings of equal length greater than just a few characters. This only gets worse in embedded, where computational resources are much more limited, and Moddable (based on my awareness of their problem domain) has to care a lot about resource usage, even more than mobile developers. If this says anything, they even wrote their own JS engine to satisfy their unique device constraints.

One final thing: config makes no sense for actual logic, and most network code that isn't application-level is 99% logic, 1% actual configuration that isn't by the user itself. Take a look at pretty much any networking library out there (like ecstatic), and you'll notice this very quickly, even for the seemingly simple stuff. Fun fact: caching is non-trivial, fault tolerance is far from obvious, and directory listings can get pretty intricate. And I didn't even get to the security or performance aspects of this here, and this is all written from the assumption that it's only one thread here, since JavaScript has no explicit facilities for multithreaded code currently. (This is where things get complicated quick.)


Isiah Meadows me at isiahmeadows.com, www.isiahmeadows.com