Skip to content

Instantly share code, notes, and snippets.

@samuelgoto
Last active October 25, 2017 00:05
Show Gist options
  • Save samuelgoto/405251eb4fd7b42d764589418ea2afc4 to your computer and use it in GitHub Desktop.
Save samuelgoto/405251eb4fd7b42d764589418ea2afc4 to your computer and use it in GitHub Desktop.

This is a very early stage 0 exploration to add enums to Javascript, as a syntatic simplication over a common pattern to define enumerations.

Introduction

Enums come up often in code bases (TODO: try to estimate a number) and it is easy to get it incorrectly. Specifically, it is easy to forget:

  • to Object.freeze the object
  • to declare it as a const which avoids having the symbol redefined

With this in mind, we propose a nem keyword to javascript, say enum which de-sugars to the following:

// if you write this ...
enum Foo {
  BAR: 1,
  HELLO "hello",
  WORLD: false
}

// ... it gets de-sugared to:
const Foo = Objec.freeze({
  BAR: 1,
  HELLO: "hello",
  WORLD: false
});

// TODO(goto): should we use Symbols here for extra type safety?
@samuelgoto
Copy link
Author

samuelgoto commented Oct 9, 2017

Usage

flow control

// isomorphic to match () ?
switch (request.status) {
  OK : // phew
  ERROR: // ugh
  default: // fine 
}

loop

for (let planet in Planet) {
  // planet
}

instaceof

bit-wise operations

@samuelgoto
Copy link
Author

samuelgoto commented Oct 9, 2017

Open Questions

  • should enums be frozen?
  • should enums be constants?
  • scoping
  • should enum values be nominal or ordinal (e.g. comparable?) or both? python enums aren't comparable.
  • @domenic: should enums be exotic objects that throw when accessing an undefined attribute?
  • @domenic: enums should probably be enabled to be strings. a huge percentage of the web APIs rely on enumeration of strings (e.g. addEventListener takes a finite set of strings, referrerPolicy, etc) and that should probably be factor into.
  • what does typeof enum return? enum? or should typeof Foo.BAR be Foo?
  • if enum Foo { BAR: 1 } and Bar { FOO: 1 }, does Foo.BAR == Bar.FOO? does Foo.BAR === Bar.FOO?
  • is Foo.BAR instanceof Foo?
  • can you iterate over them? e.g. for (let entry of Foo) { console.log(entry) }
  • can you in? e.g. Foo.BAR in Foo?
  • can you access enums programatically? e.g. Foo[0] returns Foo.BAR?
  • can you Reflect or Proxy it?
  • if you [pick two exact same names] (https://docs.python.org/3/library/enum.html#duplicating-enum-members-and-values), does it throw an Exception? e.g. enum Foo { BAR: 1, BAR: 2 }?
  • can they be used as expressions? e.g. let Foo = enum { BAR: 1 }?
  • can you use them in a switch statement (e.g. dart)?

@samuelgoto
Copy link
Author

one possible materialization of enums:

export class Foo {
    value: string;
    constructor(value: string) {
        if (__MyType_LOCK__) {
            throw new Error('Enum instances have to be initialized here!')
        }
        this.value = value;
    }
    static ONE = new Foo('one');
    static TWO = new Foo('two');
}

@samuelgoto
Copy link
Author

samuelgoto commented Oct 24, 2017

Dart

Enumerated types, often called enumerations or enums, are a special kind of class used to represent a fixed number of constant values.

For example:

enum Color {
  red,
  green,
  blue
}

Enumerated types have the following limits:
You can’t subclass, mix in, or implement an enum.
You can’t explicitly instantiate an enum.

@samuelgoto
Copy link
Author

samuelgoto commented Oct 24, 2017

Closure

Specifies an enum, which is a type with a specific finite number of possible values, often strings or numbers. @enum tag must be
followed by a type expression. If the type of an enum is omitted, number is assumed.
The type label of an enum applies to each property of the enum. For example if an enum has type number, each of its enumerated
properties must be a number.

/**
 * Enum for tri-state values.
 * @enum {number}
 */
project.TriState = {
  TRUE: 1,
  FALSE: -1,
  MAYBE: 0
};
  • accessing unknown entries throws warnings
  • writing to it doesn't throw any warnings

JSC_INEXISTENT_ENUM_ELEMENT: element BAR does not exist on this enum at line 17 character 12
console.log(Foo.BAR);
^

/**
 * Enum for tri-state values.
 * @enum {number}
 */
Foo = {
  HELLO: 1,
  WORLD: -1
};

console.log(Foo.HELLO);
console.log(Foo.BAR);

Foo.HI = "hello"

@samuelgoto
Copy link
Author

TypeScript

Enums allow us to define a set of named constants. Using enums can make it easier to document intent, or create a set of distinct
cases. TypeScript provides both numeric and string-based enums.

enum Direction {
    Up,
    Down,
    Left,
    Right,
}

They can be numeric or strings.

enum Direction {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT",
}

They can also intermingle strings and numbers, for example:

enum BooleanLikeHeterogeneousEnum {
    No = 0,
    Yes = "YES",
}

It gets transpiled into something approximately like this:

// 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

@samuelgoto
Copy link
Author

Python

from enum import Enum
class Color(Enum):
  RED = 1
  GREEN = 2
  BLUE = 3

Makes these things accessible:

>>> print(Color.RED)
Color.RED
>>> type(Color.RED)
<enum 'Color'>
>>> isinstance(Color.GREEN, Color)
True
>>>
>>> print(Color.RED.name)
RED

Support iteration, in declaration order:

>>> for shake in Shake:
...     print(shake)
...
Shake.VANILLA
Shake.CHOCOLATE
Shake.COOKIES
Shake.MINT

@unique enforces the values to be unique:

>>> from enum import Enum, unique
>>> @unique
... class Mistake(Enum):
...     ONE = 1
...     TWO = 2
...     THREE = 3
...     FOUR = 3
...
Traceback (most recent call last):
...
ValueError: duplicate values found in <enum 'Mistake'>: FOUR -> THREE

You can assign auto() values:

>>> from enum import Enum, auto
>>> class Color(Enum):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()

You can compare them:

>>> Color.RED is Color.RED
True
>>> Color.RED is Color.BLUE
False
>>> Color.RED is not Color.BLUE
True

Comparisons against non-enumeration values will always compare not equal (again, IntEnum was explicitly designed to behave differently, see below):

>>> Color.BLUE == 2
False

The first variation of Enum that is provided is also a subclass of int. Members of an IntEnum can be compared to integers; by extension, integer enumerations of different types can also be compared to each other:

@samuelgoto
Copy link
Author

Java

An enum type is a special data type that enables for a variable to be a set of predefined constants.
Because they are constants, the names of an enum type's fields are in uppercase letters.

You should use enum types any time you need to represent a fixed set of constants. That includes natural enum types such as the planets in our solar system and data sets where you know all possible values at compile time—for example, the choices on a menu, command line flags, and so on.

public enum Day {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
    THURSDAY, FRIDAY, SATURDAY 
}

The compiler automatically adds some special methods when it creates an enum. For example, they have a static values method that returns an array containing all of the values of the enum in the order they are declared.

You can iterate over them:

for (Planet p : Planet.values()) {
    System.out.printf("Your weight on %s is %f%n",
                      p, p.surfaceWeight(mass));
}

@samuelgoto
Copy link
Author

samuelgoto commented Oct 24, 2017

Examples

https://github.com/jspears/subschema/blob/master/src/listenUtil.js#L144

/**
 * The possible types of listeners that can be added.
 * @readonly
 * @enum {string}
 */
export const MapTypes = {
    'value': 'addListener',
    'error': 'addErrorListener',
    'submit': 'addSubmitListener',
    'state': 'addStateListener',
    'validate': 'addValidateListener',
    'addListener': 'addListener',
    'addErrorListener': 'addErrorListener',
    'addSubmitListener': 'addSubmitListener',
    'addStateListener': 'addStateListener',
    'addValidateListener': 'addValidateListener'
}

https://github.com/WaffleBoardWizard/guild-ball-simulator/blob/04ae96c7561e7b9c3c0959134a34f6a6c3708a58/gb-simulator-web/src/components/Controls/GameBoard/Inputs.js#L1

import Enum from "es6-enum"

export default Enum(
  "PIECE_CLICK",
  "PIECE_DRAG",
  "CLICK_MENU_BUTTON");

https://github.com/Piicksarn/cdnjs/blob/master/ajax/libs/Shuffle/2.0.6/jquery.shuffle.js#L103

jquery

/**
 * Events the container element emits with the .shuffle namespace.
 * For example, "done.shuffle".
 * @enum {string}
 */
Shuffle.EventType = {
  LOADING: 'loading',
  DONE: 'done',
  SHRINK: 'shrink',
  SHRUNK: 'shrunk',
  FILTER: 'filter',
  FILTERED: 'filtered',
  SORTED: 'sorted',
  LAYOUT: 'layout',
  REMOVED: 'removed'
};

angular

https://github.com/angular/bower-material/blob/master/modules/js/panel/panel.js#L2504

/**
 * Possible default closeReasons for the close function.
 * @enum {string}
 */
MdPanelRef.closeReasons = {
  CLICK_OUTSIDE: 'clickOutsideToClose',
  ESCAPE: 'escapeToClose',
};

https://github.com/mirror/chromium/blob/master/chrome/browser/resources/settings/site_settings/constants.js

/**
 * All possible contentSettingsTypes that we currently support configuring in
 * the UI. Both top-level categories and content settings that represent
 * individual permissions under Site Details should appear here.
 * This should be kept in sync with the |kContentSettingsTypeGroupNames| array
 * in chrome/browser/ui/webui/site_settings_helper.cc
 * @enum {string}
 */
settings.ContentSettingsTypes = {
  COOKIES: 'cookies',
  IMAGES: 'images',
  JAVASCRIPT: 'javascript',
  SOUND: 'sound',
  PLUGINS: 'plugins',  // AKA Flash.
  POPUPS: 'popups',
  GEOLOCATION: 'location',
  NOTIFICATIONS: 'notifications',
  MIC: 'media-stream-mic',  // AKA Microphone.
  CAMERA: 'media-stream-camera',
  PROTOCOL_HANDLERS: 'register-protocol-handler',
  UNSANDBOXED_PLUGINS: 'ppapi-broker',
  AUTOMATIC_DOWNLOADS: 'multiple-automatic-downloads',
  BACKGROUND_SYNC: 'background-sync',
  MIDI_DEVICES: 'midi-sysex',
  USB_DEVICES: 'usb-chooser-data',
  ZOOM_LEVELS: 'zoom-levels',
  PROTECTED_CONTENT: 'protectedContent',
  ADS: 'ads',
};

Azure

https://github.com/Azure/azure-storage-node/blob/master/lib/common/util/constants.js

/**
  * Defines the service types indicators.
  * 
  * @const
  * @enum {string}
  */
  ServiceType: {
    Blob: 'blob',
    Queue: 'queue',
    Table: 'table',
    File: 'file'
  },

  /**
  * Specifies the location used to indicate which location the operation can be performed against.
  *
  * @const
  * @enum {int}
  */
  RequestLocationMode: {
    PRIMARY_ONLY: 0,
    SECONDARY_ONLY: 1,
    PRIMARY_OR_SECONDARY: 2
  },

  /**
  * Represents a storage service location.
  *
  * @const
  * @enum {int}
  */
  StorageLocation: {
    PRIMARY: 0,
    SECONDARY: 1
  },

@samuelgoto
Copy link
Author

BigQuery

SELECT
  sample_path,
  sample_repo_name,
  content
FROM [bigquery-public-data:github_repos.sample_contents]
WHERE
  sample_path LIKE '%.js' and
  content LIKE '%@enum%' and
  not content LIKE '%goog%'
  
limit 100;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment