Enums?
On Oct 1, 2011, at 4:13 AM, Axel Rauschmayer wrote:
One language feature from JavaScript that I miss are enums. Would it make sense to have something similar for ECMAScript, e.g. via Lisp-style/Smalltalk-style symbols plus type inference? If yes, has this been discussed already? I feel strange when I simulate symbols with strings.
proposals:enumeration_type, proposals:switch_class, discussion:switch_class, discussion:switch_class#older_enum_proposal
All ES4-era, pretty musty.
On Fri, Sep 30, 2011 at 10:13 PM, Axel Rauschmayer <axel at rauschma.de> wrote:
One language feature from JavaScript that I miss are enums. Would it make sense to have something similar for ECMAScript, e.g. via Lisp-style/Smalltalk-style symbols plus type inference? If yes, has this been discussed already? I feel strange when I simulate symbols with strings.
ISTM private name objects make a pretty nice stand-in for symbols.
On Sep 30, 2011, at 7:33 PM, Dean Landolt wrote:
On Fri, Sep 30, 2011 at 10:13 PM, Axel Rauschmayer <axel at rauschma.de> wrote: One language feature from JavaScript that I miss are enums. Would it make sense to have something similar for ECMAScript, e.g. via Lisp-style/Smalltalk-style symbols plus type inference? If yes, has this been discussed already? I feel strange when I simulate symbols with strings.
ISTM private name objects make a pretty nice stand-in for symbols.
function Enum(...names) { let e = Object.create(); names.forEach(function(n) {e[n] = Name.create()}); return Object.freeze(e); }
...
const colors = Enum('red,'blue','green',yellow',orange','purple'); backgournd = colors.red; foreground = colors.blue; ...
Nice one!
It doesn’t even have to be Name.create() – new Object() works just fine. And you can use switch with this pattern (I ES5-ified the code):
function Enum() { let e = {}; Array.prototype.forEach.call(arguments, function(name) { e[name] = new Object(); }); return Object.freeze(e); }
var color = Enum("red", "green", "blue"); var c = color.green; switch(c) { case color.red: alert("red"); break; case color.green: alert("green"); break; }
Channeling for MarkM, if you use regular objects for the enumeration element values you probably will want to freeze them all so they can't be used as a communications channel.
You may also want to define them with a null [[Prototype]]. I intended to do that for e in my original formulation for it got lost along the way from my mind to my finders. For debugging output you might want to capture the string of each numeration element value and provide a toString method.
A couple reactions:
-
strings are already interned in current engines for symbol-like performance; there's no need to introduce symbols into the language
-
private names are overkill for most uses of enums; just use string literals
-
in SpiderMonkey I think you get better performance if your switch cases use known constants; for example:
const RED = "red", GREEN = "green", BLUE = "blue"; ... switch (color) { case RED: ... case GREEN: ... case BLUE: ... }
-
with modules, you would be able to define these consts and share them modularly (currently in SpiderMonkey the only way to share these definitions across modules as consts is either to make them global or to share an eval-able string that each module can locally eval as a const declaration -- blech)
On Oct 3, 2011, at 10:17 AM, David Herman wrote:
A couple reactions:
- strings are already interned in current engines for symbol-like performance; there's no need to introduce symbols into the language
Is this really true? It was my original assumption based upon string semantics. However, I was latter lead to believe that many engines don't routinely intern/canonicalize strings and hence string equality comparisons can't be assume to have the performance of pointer comparisons. For example, it was my understanding that SpiderMonkey uses interned strings for property lookup but that it doesn't necessarily intern non-property key strings??
No engine interns computed strings unconditionally. Consider concatenated strings, the silly benchmarks that s += s for s = "x" up to 20 or more times.
Engines do optimize string comparison to compare pointers first, in case both comparands are interned. Engines also know when a string is interned so they can optimize accordingly. Engines also intern small int literal strings, e.g.
Channeling for MarkM, if you use regular objects for the enumeration element values you probably will want to freeze them all so they can't be used as a communications channel.
Immutability makes sense. I don’t understand the "communications channel" argument.
You may also want to define them with a null [[Prototype]]. I intended to do that for e in my original formulation for it got lost along the way from my mind to my finders.
What’s the benefit? Is comparison faster? It can’t be about memory consumption (right?).
For debugging output you might want to capture the string of each numeration element value and provide a toString method.
Good idea! With a null prototype, such a method would be needed to make the objects printable.
We are approaching Bloch’s solution in "Effective Java".
- strings are already interned in current engines for symbol-like performance; there's no need to introduce symbols into the language
True.
private names are overkill for most uses of enums; just use string literals
in SpiderMonkey I think you get better performance if your switch cases use known constants; for example:
const RED = "red", GREEN = "green", BLUE = "blue"; ... switch (color) { case RED: ... case GREEN: ... case BLUE: ... }
What makes Allen’s pattern appealing is that an enum becomes a set of symbols. You then can look up your options and enumerate the set members. You don’t have a flat set of symbols, either, you have sets of related symbols.
Are you saying that comparing interned strings is faster than comparing two objects (supposedly by comparing their object IDs)? If so, Enum() could be adapted to:
function Enum() { let e = {}; Array.prototype.forEach.call(arguments, function(name) { e[name] = name; }); return Object.freeze(e); }
On Oct 3, 2011, at 6:26 PM, Axel Rauschmayer wrote:
Channeling for MarkM, if you use regular objects for the enumeration element values you probably will want to freeze them all so they can't be used as a communications channel.
Immutability makes sense. I don’t understand the "communications channel" argument.
A "bad guy" is passed a enumeration value. The bad guy has been given local access to some secret but that is ok because he has no way to pass the secret on to anybody else. The bad guy notices that he can add a new property to the enumeration value. So he does so and assigns the secret as the value of the property. The same enumeration value may subsequent be passed to a cohort of the bad guy who looks for the added property and retrieves the secret
You may also want to define them with a null [[Prototype]]. I intended to do that for e in my original formulation for it got lost along the way from my mind to my finders.
What’s the benefit? Is comparison faster? It can’t be about memory consumption (right?).
Nothing about perf. I just wanted these objects to be as close to immutable values as possible, just like private names. I don't see any need for them to inherit from mutable Object.prototype.
However, if I was going to have the toString then I would probably create a shared (immutable) prototype to hold the toString method. Each individual would have a immutable string valued property containing the name of the value. You might also want to have a reference back to the Enum object that contains the value
For debugging output you might want to capture the string of each numeration element value and provide a toString method.
Good idea! With a null prototype, such a method would be needed to make the objects printable.
We are approaching Bloch’s solution in "Effective Java".
There are only a few ways to do these things.
In database world enums are uint/ushort (up to 0xFFFF) and two enums may conflicts due same order but this has never been a problem.
var Enum = (function () { "use strict"; function assign(name, i) { this[name] = i + 1; } var forEach = [].forEach, freeze = Object.freeze || function(o){return o} ; return function Enum() { forEach.call(arguments, assign, this); return freeze(this); }; }());
var colors = new Enum("red", "green", "blue");
Hope it was useful.
Best , Andrea Giammarchi
FWIW: I’ve blogged about this. www.2ality.com/2011/10/enums.html
/** We don’t want the mutable Object.prototype in the prototype chain */ exports.Symbol.prototype = Object.create(null); exports.Symbol.prototype.constructor = exports.Symbol;
If you don't want Object.prototype (why ?) do you want an enumerable, configurable, writable constructor ?
Why not just this ? exports.Symbol.prototype = Object.create(null, {constructor: {value: exports.Symbol}});
Also why so many runtime closures and "var that = this" ?
function recycledF1(name) { this.s[name] = new exports.Symbol(name, this.o[name]); }
function recycledF2(name) { this[name] = new exports.Symbol(name); }
function recycledF3(key) { return this[key]; }
exports.Enum = function (obj) { if ( arguments.length === 1 && // (obj !== null && typeof obj === "object") // 2 checks for undefined obj != null && typeof obj === "object" ) { Object.keys(obj).forEach(recycledF1, {s:this, o:obj}); } else { [].forEach.call(arguments, recycledF2, this); } Object.freeze(this); }
exports.Enum.prototype.symbols = function() { return Object.keys(this).map(recycledF3, this); }
I would also drop all these anonymous functions using the closure with declarations for Symbol and Enum and export once at the end rather than reference the exports object each time.
This is because I like the idea and I would like to make it as small as possible once minified and as memory/performances improved as possible for mobile.
What do you think ?
Best , Andrea Giammarchi
If you don't want Object.prototype (why ?)
do you want an enumerable, configurable, writable constructor ?
I don’t care much either way. The main point for me is that symbol instances are immutable. If all of the instance’s prototype are also immutable, the instance itself is "more immutable". Again, I don’t have strong feelings for that.
Why not just this ?
exports.Symbol.prototype = Object.create(null, {constructor: {value: exports.Symbol}});
I don’t see much difference with my solution, either regarding conciseness or regarding performance.
Also why so many runtime closures and "var that = this" ?
Runtime closures:
- Does performance really matter here? I find my solution easier to read.
- Note that binding "this" via forEach() and map() also has performance implications.
that = this:
- Your point is that forEach() and map() have a second parameter that allows you to hand in a value for "this", right? => I have yet to find a clear argument in favor or against using that = this here. Both solutions have performance implications, both solutions add an extra line. that = this has two extra tokens, but adding a second argument looks awkward.
I would also drop all these anonymous functions using the closure with declarations for Symbol and Enum and export once at the end rather than reference the exports object each time.
Yes, good idea, it’ll make things easier to read.
I'll bench it, about size this is 893 VS 914 ... not a big deal but scope lookup is easier in my version.
Please consider this.enum break all minifiers since enum is a reserved word, I had to do this["enum"] = {} at the end indeed.
Look if you find some hint here
(function (Object, exports) {
/**
* A simple enum implementation for JavaScript
* @link http://www.2ality.com/2011/10/enums.html
* @author @rauschma
* @revision @WebReflection
* @note enum is reserved word. Use var enm = this.enum or var
enm = require("enum"); */
var // common shortcut
EnumPrototype = Enum.prototype,
defineProperty = Object.defineProperty,
forEach = [].forEach,
freeze = Object.freeze,
getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor,
getOwnPropertyNames = Object.getOwnPropertyNames,
keys = Object.keys
;
// module constructors
function Enum(obj) {
if (arguments.length == 1 && obj != null && typeof obj == "object")
{ forEach.call(keys(obj), assignSymbolProperties, {s: this, t: obj} ); } else { forEach.call(arguments, assignSymbol, this); } freeze(this); }
function Symbol(name, props) {
this.name = name;
props && copyOwnTo(props, this);
freeze(this);
}
// helpers
function assignSymbol(name) {
this[name] = new Symbol(name);
}
function assignSymbolProperties(name) {
this.s[name] = new Symbol(name, this.t[name]);
}
function copyOwnTo(source, target) {
var names = getOwnPropertyNames(source);
names && forEach.call(names,
copyEachProperty,
{s: source, t: target}
);
return target;
}
function copyEachProperty(propName) {
defineProperty(
this.t,
propName,
getOwnPropertyDescriptor(
this.s,
propName
)
);
}
function mapSymbolValue(key) {
return this[key];
}
// secure Symbol prototype
freeze(
Symbol.prototype = Object.create(null, {
constructor: {
value: Symbol
},
toString: {
value: function toString() {
return "|"+this.name+"|";
}
}
})
);
// extend Enum prototype
defineProperty(EnumPrototype, "contains", {
enumerable: true,
value: function contains(sym) {
return sym instanceof Symbol ?
this[sym.name] === sym :
false
;
}
});
defineProperty(EnumPrototype, "symbols", {
enumerable: true,
value: function symbols() {
return keys(this).map(mapSymbolValue, this);
}
});
// exports
exports.Enum = Enum;
exports.Symbol = Symbol;
}(Object, typeof exports == "undefined" ? this["enum"] = {} : exports));
Best , Andrea Giammarchi
On Oct 24, 2011, at 2:53 PM, Axel Rauschmayer wrote:
that = this:
- Your point is that forEach() and map() have a second parameter that allows you to hand in a value for "this", right? => I have yet to find a clear argument in favor or against using that = this here. Both solutions have performance implications, both solutions add an extra line. that = this has two extra tokens, but adding a second argument looks awkward.
We're talking about performance, not "looks". Passing trailing |this| parameters to the Array extras avoids allocation (bound object or closure) per extra application.
demonstrated here indeed: jsperf.com/array-extras-second-argument
on average, closures plus scope lookup looks always slower but I'll add more tests.
Ah, the array extras use pass the second argument on to Function.prototype.call. That would indeed be faster. I thought bind() was used, but that makes no sense.
it must be said the pattern I chose to bring more references ( e.g. the object with .s and .t ) looks slower on average but I am big fun of code reusability so I would say two or more functions that brings same references through the object in order to perform same operations could be summarized as single one ( kinda edge case though )
If only two references I would still prefer the "this" approach for one of them rather than a lookup for both but for all other cases I said long time ago this trend to create a new closure rather than use the second argument of Array extras could not bring any advantage for both reusability and, now demonstrated, performances.
It was nice to test it in any case so thanks.
br, Andrea Giammarchi
Please consider this.enum break all minifiers since enum is a reserved word, I had to do this["enum"] = {} at the end indeed.
Is "enums" better than "enum"?
yes, even if in a non RequireJS world accessing Enum constructor via enums.Enum does not sound right to me
it must be said the pattern I chose to bring more references ( e.g. the object with .s and .t ) looks slower on average but I am big fun of code reusability so I would say two or more functions that brings same references through the object in order to perform same operations could be summarized as single one ( kinda edge case though )
If only two references I would still prefer the "this" approach for one of them rather than a lookup for both but for all other cases I said long time ago this trend to create a new closure rather than use the second argument of Array extras could not bring any advantage for both reusability and, now demonstrated, performances.
It was nice to test it in any case so thanks.
I’m a big fan of code reuse and eliminating redundancy. However, I find that there is also such a thing as being too clever about it. For example, in functional programming there are some incredibly sophisticated things you can achieve, sometimes at the expense of easily understanding the code. Some foldr and foldl uses come to mind. So it’s always a balance.
I’ve incorporated several of your suggestions: rauschma/enums/blob/master/enums.js
Looking at your alternate implementation, I’m not sure that we’ll find common ground on all issues, though. My goal is making things as easy to understand as possible (highly subjective, I know). Your goal seems to be performance.
Thanks for the feedback!
Axel
I work on mobile browsers ... things are up to 1000 times slower there and all tricks, as long as still readable, are welcome in my daily basis work.
Best , Andrea Giammarchi
On Oct 25, 2011, at 8:15 , Andrea Giammarchi wrote:
I work on mobile browsers ... things are up to 1000 times slower there and all tricks, as long as still readable, are welcome in my daily basis work.
Right, mobile is challenging.
On Monday, 2011-10-03 at 19:17 , David Herman wrote:
A couple reactions:
- strings are already interned in current engines for symbol-like performance; there's no need to introduce symbols into the language
Assuming that's about Ruby like symbols or Clojure like keywords. While they are not much different from strings I really miss them in JS. Also I think clojure uses them in a very interesting way:
(use 'clojure.contrib.shell-out :as shell :reload)
or even more intensive
(ns com.my-company.clojure.examples.my-utils (:import java.util.Date) (:use [clojure.contrib.def :only (defvar-)]) (:require [clojure.contrib.shell-out :as shell]))
Problem with strings is that it's not always obvious weather it's intended argument to the function or a flag.
One language feature from JavaScript that I miss are enums. Would it make sense to have something similar for ECMAScript, e.g. via Lisp-style/Smalltalk-style symbols plus type inference? If yes, has this been discussed already? I feel strange when I simulate symbols with strings.