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:

```cs
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:

```cs
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:

```cs
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:

```ts
// 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:

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

> -----Original Message-----
> From: es-discuss [mailto:[email protected]] On Behalf Of 
> Brendan Eich
> Sent: Wednesday, December 16, 2015 10:04 AM
> To: Alican Çubukçuoğlu <[email protected]>; es- 
> [email protected]
> Subject: Re: Re: Propose simpler string constant
> 
> On Wed, Dec 16, 2015 at 9:41 AM Alican Çubukçuoğlu 
> <[email protected] <mailto:[email protected]> >
> wrote:
> 
> 
>       How are enums declared?
>       ```javascript
>       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.
> 
> /be
_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to