Skip to content

Instantly share code, notes, and snippets.

@rauschma
Last active October 6, 2023 18:30
Show Gist options
  • Save rauschma/f3e77132319e09b94722 to your computer and use it in GitHub Desktop.
Save rauschma/f3e77132319e09b94722 to your computer and use it in GitHub Desktop.
Enum proposal

Advantages compared to using symbols as enum values:

  • Enum values are much more customizable. For example, one can have custom prototype and/or instance methods.
  • Enum values get two custom properties:
    • name provides direct access to the name of an enum value.
    • ordinal holds a number, the position of the enum value. Useful for some applications.
  • One can use instanceof to test whether a value is an element of an enum.
  • One occasionally requested feature for enums is that enum values be numbers (e.g. for flags) or strings (e.g. to compare with values in HTTP headers). That can be achieved by making those values properties of enum values. For an example, see enum Mode, below.

Static properties of enums:

  • enumValues an Array with all enum values
  • enumValueOf(str) maps the name of an enum value to the value (useful for parsing, e.g. from JSON data)

Implementation:

  • enumify: this proposal, as an ES6 library

Open questions:

  • Provide constructor arguments via RED(arg0, arg1, ...)?
  • Make it impossible to subclass enums?
  • Not ideal: enum values and prototype methods are not well separated.
    • Even worse with enum values like RED(...) {...}
enum Color {
RED { prop: 'x' },
GREEN { prop: 'y' },
BLUE { prop: 'z' },
}
class Enum {
static enumValueOf(name) {
if (this.constructor === Enum) {
throw new TypeError('This method is abstract');
}
return this.enumValues.find(x => x.name === name);
}
static [Symbol.iterator]() {
return this.enumValues[Symbol.iterator]();
}
toString() {
return `${this.constructor.name}.${this.name}`;
}
}
class Color extends Enum {}
const entries = {
RED: { prop: 'x' },
GREEN: { prop: 'y' },
BLUE: { prop: 'z' },
};
Object.defineProperty(Color, 'enumValues', {
value: [],
configurable: false,
writable: false,
enumerable: true,
});
for (const [ordinal, name] of Object.keys(entries).entries()) {
const value = new Color();
value.name = name;
value.ordinal = ordinal;
Object.assign(value, entries[name]);
Color.enumValues.push(value);
Object.defineProperty(this, name, {
value: value,
configurable: false,
writable: false,
enumerable: true,
});
}
delete Color.[[Construct]];
//-------------------------------------------------
enum Color {
RED, GREEN, BLUE
}
assert.ok(Color.RED instanceof Color);
assert.strictEqual(String(Color.RED), 'Color.RED');
assert.strictEqual(Color.GREEN.ordinal, 1);
assert.strictEqual(Color.enumValueOf('BLUE'), Color.BLUE);
assert.deepStrictEqual(Color.enumValues, [Color.RED, Color.GREEN, Color.BLUE]);
assert.throws(() => {
// Can’t create new instances
new Color();
});
//-------------------------------------------------
// Alas, data properties don’t work, because the enum
// values (TicTacToeColor.X etc.) don’t exist when
// the property definitions are evaluated.
enum TicTacToeColor {
O {
get inverse() { return TicTacToeColor.X }
},
X {
get inverse() { return TicTacToeColor.O }
},
}
assert.strictEqual(TicTacToeColor.X.inverse, TicTacToeColor.O);
assert.strictEqual(TicTacToeColor.O.inverse, TicTacToeColor.X);
assert.strictEqual(String(TicTacToeColor.O), 'TicTacToeColor.O');
assert.strictEqual(TicTacToeColor.O.ordinal, 0);
assert.strictEqual(TicTacToeColor.X.ordinal, 1);
//-------------------------------------------------
enum Weekday {
MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY, SUNDAY;
isBusinessDay() {
switch (this) {
case Weekday.SATURDAY:
case Weekday.SUNDAY:
return false;
default:
return true;
}
}
}
assert.strictEqual(Weekday.SATURDAY.isBusinessDay(), false);
assert.strictEqual(Weekday.MONDAY.isBusinessDay(), true);
//-------------------------------------------------
enum Result {
ACCEPTED, REJECTED
}
enum State {
START {
enter(iter) {
const {value,done} = iter.next();
if (done) {
return Result.REJECTED;
}
switch (value) {
case 'A':
return State.A_SEQUENCE;
default:
return Result.REJECTED;
}
}
},
A_SEQUENCE {
enter(iter) {
const {value,done} = iter.next();
if (done) {
return Result.REJECTED;
}
switch (value) {
case 'A':
return State.A_SEQUENCE;
case 'B':
return State.B_SEQUENCE;
default:
return Result.REJECTED;
}
}
},
B_SEQUENCE {
enter(iter) {
const {value,done} = iter.next();
if (done) {
return State.ACCEPT;
}
switch (value) {
case 'B':
return State.B_SEQUENCE;
default:
return Result.REJECTED;
}
}
},
ACCEPT {
enter(iter) {
return Result.ACCEPTED;
}
},
}
function runStateMachine(str) {
let iter = str[Symbol.iterator]();
let state = State.START;
while (true) {
state = state.enter(iter);
switch (state) {
case Result.ACCEPTED:
return true;
case Result.REJECTED:
return false;
}
}
}
assert.strictEqual(runStateMachine('AABBB'), true, 'AABBB');
assert.strictEqual(runStateMachine('AA'), false, 'AA');
assert.strictEqual(runStateMachine('BBB'), false, 'BBB');
assert.strictEqual(runStateMachine('AABBC'), false, 'AABBC');
assert.strictEqual(runStateMachine(''), false, '');
//-------------------------------------------------
enum Mode {
USER_R {
n: 0b100000000,
},
USER_W {
n: 0b010000000,
},
USER_X {
n: 0b001000000,
},
GROUP_R {
n: 0b000100000,
},
GROUP_W {
n: 0b000010000,
},
GROUP_X {
n: 0b000001000,
},
ALL_R {
n: 0b000000100,
},
ALL_W {
n: 0b000000010,
},
ALL_X {
n: 0b000000001,
},
}
assert.strictEqual(
Mode.USER_R.n | Mode.USER_W.n | Mode.USER_X.n |
Mode.GROUP_R.n | Mode.GROUP_X.n |
Mode.ALL_R.n | Mode.ALL_X.n,
0o755);
assert.strictEqual(
Mode.USER_R.n | Mode.USER_W.n | Mode.USER_X.n | Mode.GROUP_R.n,
0o740);
@alexsandro-xpt
Copy link

How could I get an Enum by an integer value?

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