Closed jhlange closed 7 years ago
@impinball :+1: +9001 I love this proposal.
I seldom use the enum
type because it is only numeric, and it almost never makes sense to pass numeric values to/from e.g. JSON, web services, etc. This (along with #1003) would make enums actually practical to use, and remove the need for separate adapter/serializer classes.
+1
:+1:
I forgot about that part. Will fix as soon as I get to a computer.
On Sun, Sep 20, 2015, 09:26 Jon notifications@github.com wrote:
@impinball https://github.com/impinball I'd suggest moving the proposal into a repo. Remove the 'void' part as its not reasonable to implement since 'undefined' is used to know if a value can be resolved statically. Haven't looked at implementing non-primitives types so no comments there yet other then 'seems doable'.
— Reply to this email directly or view it on GitHub https://github.com/Microsoft/TypeScript/issues/1206#issuecomment-141785283 .
@jbondc Done and done. I was considering it for a bit, anyways. I've just been busy these past few weeks (school, trying to get a new web dev startup off the ground, etc.).
Forked :wink: I'll send a pull request as I get around to it.
Just FYI: With the current compiler (test it in the playground on typescriptlang.org), you can write this:
export enum Language {
English = <any>"English",
German = <any>"German",
French = <any>"French",
Italian = <any>"Italian"
}
Which generates:
(function (Language) {
Language[Language["English"] = "English"] = "English";
Language[Language["German"] = "German"] = "German";
Language[Language["French"] = "French"] = "French";
Language[Language["Italian"] = "Italian"] = "Italian";
})(exports.Language || (exports.Language = {}));
var Language = exports.Language;
Isn't this what we need?
In VS, IntelliSense works for me... and I do not need to assign string to lang
; you can assign using lang = Language.French
... But of course, a compiler feature would be better... however, it solves the problem for me..
The workaround I've been using : https://basarat.gitbooks.io/typescript/content/docs/tips/stringEnums.html :rose:
@rsuter you found a great workaround for string enum, unfortunally it doesn't work with const enum, my primary usage of string enum would be for inline const values without need of transpile code..
Ping on my proposal.. I wish I could get a little more critiquing on it.
@impinball, we discussed the matter at our last design meeting (#5740). We're trying to come up with a future-proof idea where enums might individually have their own types as well, and so it's not clear exactly what the most ideal way to do that is while allowing values other than primitive literals.
@DanielRosenwasser How does that factor into my proposal? I'm willing to withdraw it if it's not very good.
It isn't bad at all, but if we take enums in a direction where each enum member claims a type representing its value (e.g. a string enum creating a string literal type alias for each member), it isn't clear what kind of literal type we would create for each member of a non-primitive enum. That would be a weird inconsistency between a string
enum and a Dog
enum.
Okay. I'm guessing you're talking in the same vein of string literal types,
like type Foo = "foo" | "bar"
?
On Mon, Nov 23, 2015, 23:17 Daniel Rosenwasser notifications@github.com wrote:
It isn't bad at all, but if we take enums in a direction where each enum member claims a type representing its value (e.g. a string enum creating a string literal type alias for each member), it isn't clear what kind of literal type we would create for each member of a non-primitive enum. That would be a weird inconsistency between a string enum and a Dog enum.
— Reply to this email directly or view it on GitHub https://github.com/Microsoft/TypeScript/issues/1206#issuecomment-159145530 .
It is more like trying make string literal types and enums make sense togather.
Any chance this will be implemented?
Just as an update, we're still actively discussing this, but the upcoming features in 2.0 have had priority, so no solid plans yet.
Just a note on this.
Sometimes it's very useful to have methods attached to the enum. As an example take a look at this groovy code that does a custom reverse lookup:
public enum ColorEnum {
WHITE('white', 'White is mix of all colors'),
BLACK('black', 'Black is no colors'),
RED('red', 'Red is the color of blood')
final String id;
final String desc;
static final Map map
static {
map = [:] as TreeMap
values().each{ color ->
println "id: " + color.id + ", desc:" + color.desc
map.put(color.id, color)
}
}
private ColorEnum(String id, String desc) {
this.id = id;
this.desc = desc;
}
static getColorEnum( id ) {
map[id]
}
}
@rauschma has another good example using his library enumify:
class Weekday extends Enum {
isBusinessDay() {
switch (this) {
case Weekday.SATURDAY:
case Weekday.SUNDAY:
return false;
default:
return true;
}
}
}
Weekday.initEnum([
'MONDAY', 'TUESDAY', 'WEDNESDAY',
'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY']);
console.log(Weekday.SATURDAY.isBusinessDay()); // false
console.log(Weekday.MONDAY.isBusinessDay()); // true
Is there any chance we could have something similar for Typescript as well?
EDIT: Just noticed that the two examples are somehow misleading. I think the current proposal will allow us to have methods attached to the constant:
class TokenType {
constructor(public kind: string, public length: number) {}
myMethod() {
return;
}
}
enum TokenTypes: TokenType {
OpenCurved = new TokenType('(', 1)
}
TokenTypes.OpenCurved.myMethod();
what I'm suggesting is methods attached to the enum:
class TokenType {
constructor(public kind: string, public length: number) {}
}
enum TokenTypes: TokenType {
OpenCurved = new TokenType('(', 1)
fromString(string: tokenKind) {
return reverseLookupTable.find(token => token.kind === tokenKind);
}
}
token = TokenTypes.fromString('(');
token === TokenTypes.OpenCurved
@danielepolencic you can add static methods (documented here as well https://basarat.gitbooks.io/typescript/content/docs/enums.html) :
enum Weekday {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}
namespace Weekday {
export function isBusinessDay(day: Weekday) {
switch (day) {
case Weekday.Saturday:
case Weekday.Sunday:
return false;
default:
return true;
}
}
}
const mon = Weekday.Monday;
const sun = Weekday.Sunday;
console.log(Weekday.isBusinessDay(mon)); // true
console.log(Weekday.isBusinessDay(sun)); // false
:rose:
Thank you for that. I never thought about it 🙇
I think the only outstanding item would be to found a way to do the reverse lookup.
I'll provide an example so that I can shed some light on how I use enums.
Imagine you have a <select>
with 3 <option>
s: High, Low and Medium. Since those are constants, I'd code them as an enum:
enum Frequency {High, Medium, Low}
At this point, I wish to render the <select>
in the page. Unfortunately, I can't enumerate the constants in the enum so I end up doing:
<select>
<option value="{{Frequency.High}}">{{Frequency.High}}</option>
<option value="{{Frequency.Low}}">{{Frequency.Low}}</option>
<option value="{{Frequency.Medium}}">{{Frequency.Medium}}</option>
</select>
Time to write some tests. I'd like to grab the value from the current selection, transform it into an enum and compare it. Ideally I'd write something like this:
// dispatch click and select Low
const currentValue: string = document.querySelector('select').value;
const frequency: Frequency = Frequency.fromString(currentValue);
expect(frequency).toEqual(Frequency.Low);
With the current enum implementation, I can't reverse lookup the string value.
The other issue is connected to the value in the <option>
tag. I wish I could use an identifier rather than the value of the constant. E.g.:
<select>
<option value="{{Frequency.High.id}}">{{Frequency.High.name}}</option>
<option value="{{Frequency.Low.id}}">{{Frequency.Low.name}}</option>
<option value="{{Frequency.Medium.id}}">{{Frequency.Medium.name}}</option>
</select>
where the constant is a tuple [name, id]
. Since now I can retrieve a constant by name
and id
, I'd also like to write a new method that does that for me:
enum Frequency {
fromName(name: string): Frequency {}
fromId(id: number): Frequency {}
}
Again, I think this is not doable in the current proposal.
What I'm proposing is an enum implementation more similar to Java/Groovy enums. That would solve the issues above.
@danielepolencic BTW, current enums are technically number subtypes, and I don't see anything innately helpful in making Java-like enums, which are instanceof
the enum type itself. Also, it doesn't solve the problem of primitive string enums, which would be helpful for, say, diagnostic messages.
@danielepolencic You can do reverse lookup as Frequency["High"]
and Frequency[0]
both works. Enumeration also works:
enum Frequency { High, Medium, Low }
for (const key in Frequency) if (isNaN(+key)) console.log(key);
// High, Medium, Low
@danielepolencic as mentioned reverse lookup is supported out of the box :
enum Tristate {
False,
True,
Unknown
}
console.log(Tristate[0]); // "False"
console.log(Tristate[Tristate.False]); // "False" because `Tristate.False == 0`
More : https://basarat.gitbooks.io/typescript/content/docs/enums.html#enums-and-strings :rose:
+1 for string enums.
I'd expect the following ( which actually work if you ignore the errors ;) )
export enum SortDirection {
asc = 'asc',
desc = 'desc'
}
I don't know any JS dev who ever wants numbers in this scenario.
I'd like it if the type of SortDirection
was a string literal type ('asc'|'desc'
) instead of just string
; or at least if 'asc'|'desc'
was implicitly coercable to SortDirection
.
I really was expecting the following to work:
enum Direction {
Up,
Down,
Left,
Right
}
enum Direction {
Sideways,
InCircles
}
In my opinion, having enums represented by numbers was a mistake - it doesn't work well with declaration merging. Strings would have had a much lower chance of colliding - they would also have been easier to debug, and they are much more commonly used in everyday JS.
Anyways, for backwards compatibility, you could preserve the current enum behavior (enum
would be inferred as enum<number>
for BC) and simply add generic enum types, e.g.:
enum<string> Direction {
Up,
Down,
Left,
Right
}
enum<string> Direction {
Sideways,
InCircles
}
These would be much simpler to merge and even check the merge at compile-time to make sure the same name was only defined once. They would auto-initialize as strings, much the same way enums work for numbers now, and of course you'd be allowed to initialize them with a string expression yourself.
If somebody wants an enum<Foo>
or even enum<any>
for some reason, more power to them - an enum is simply a set of named values. It doesn't need to be more than that, because we still have classes:
class Color {
constructor(red, green, blue) { ... }
static Red = new Color(255, 0, 0)
static Green = new Color(0, 255, 0)
static Blue = new Color(0, 0, 255)
}
That works just as well - in fact, that's what I'm doing now, since numbered enums don't work well for me, and this works better in terms of declaration merging as well.
I don't know, I don't think we need to come up with something massively complicated for this - we still have classes covering a lot of these requirements, just open up enums to other types besides numbers and I'd be happy :-)
Is there any process?
@basarat I was just playing with the reverse lookup and still can get my head around this:
enum Level {Medium, Low, High}
function printVolume(level: Level): void {
console.log(`volume is ${level}`);
}
const currentVolume = 'Medium';
printVolume(Level[currentVolume]);
throws an error complaining that Element implicitly has an 'any' type because index expression is not of type 'number'
.
I tried to cast it to Level
, didn't work either.
(Yes, I have "noImplicitAny": true
in my tsconfig.json
)
@danielepolencic that has more to do with allowing using string literal types for element accesses: #6080
I like the workarounds proposed here, but I couldn't get them to easily work with function overloading the way I wanted them to, so I came up with this. It's verbose, and possibly more complicated than it needs to be, but it gives the compiler enough information that it lets me express things the way I expected to be able to:
Here's the playground link, and here is the code:
type MyEnum = "numberType" | "stringType" | "objectType";
// The advantage of declaring this class is that we could add extra methods to it if we wanted to.
class MyEnumClass {
NumberType: "numberType" = "numberType";
StringType: "stringType" = "stringType";
ObjectType: "objectType" = "objectType";
// You could declare methods here to enumerate the keys in the enum, do lookups, etc.
}
const MyEnum = new MyEnumClass();
// You can use it like a normal enum:
let x : string = MyEnum.NumberType;
console.log(x); // prints "numberType"
// But you can also use it for operator overloading:
function someFunc(x : "numberType") : number;
function someFunc(x : "stringType") : string;
// This declaration is equivalent to the above, and personally I find it more readable
function someFunc(x : typeof MyEnum.StringType) : string;
function someFunc(x : "objectType") : Object;
function someFunc(x : MyEnum) : number | string | Object;
function someFunc(x : MyEnum) : number | string | Object
{
switch(x){
case MyEnum.NumberType:
return 5;
case MyEnum.StringType:
return "a string";
case MyEnum.ObjectType:
return {foo : "bar"};
}
}
let someNumber1 : number = someFunc(MyEnum.NumberType)
let someString : string = someFunc(MyEnum.StringType);
let someObject : Object = someFunc(MyEnum.ObjectType);
// And this errors with "Type 'string' is not assignable to type 'number'" just as we would expect:
let someNumber2 : number = someFunc(MyEnum.StringType);
Things have shifted a bit with the introduction of string literal types. Picking up from @isiahmeadows 's proposal above https://github.com/Microsoft/TypeScript/issues/1206#issuecomment-121926668 , considering a restricted "string-only" enum that would behave as follows
// Please bikeshed syntax!
enum S: string {
A,
B = "X",
C
}
would be exactly equivalent to this code:
namespace S {
export const A: "A" = "A";
export type A = "A";
export const B: "X" = "X";
export type B = "X";
export const C: "C" = "C";
export type C = "C";
// Imagine this were possible
[s: string]: S;
}
type S = S.A | S.B | S.C;
the equivalent code starting with const enum S: string {
would be identical in the type system, but not have any emit. I'll sketch up an implementation in the next few weeks and we'll see how it looks.
To clarify:
[…] the equivalent code starting with const enum S: string { would be identical in the type system, but not have any emit.
Does that mean that the following code …
const enum S: string {
A,
B = "X",
C
};
var strings = [S.A, S.B, S.C];
… would be transpiled with inlined string values, like this?
var strings = ["A", "X", "C"];
Correct
Great, that would be useful in many situations. Very much looking forward to further work on this one!
I could see that as potentially useful myself. I've been personally toying with the idea of rewriting the DOM API definitions from the ground up with the latest TS, and that would also help clean up many of the occasionally magical string and boolean parameters used all over the place in it so they're more descriptive.
(IIRC const enums work as expected in definition files, correct?)
On Fri, Aug 19, 2016, 12:28 Marius Schulz notifications@github.com wrote:
Great, that would be useful in many situations. Very much looking forward to further work on this one!
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/Microsoft/TypeScript/issues/1206#issuecomment-241065785, or mute the thread https://github.com/notifications/unsubscribe-auth/AERrBLcSvWekopof3YsYZCtBP6vGiz7Nks5qhdmQgaJpZM4C9e4r .
@RyanCavanaugh I'm hoping Object.keys
, Object.values
, and Object.entries
behave sanely for your revised enums (they should, as long as nothing extra gets tacked on).
The current implementation of enums has properties for both keys and values (for reverse-lookup), so you get a mixture of both when iterating.
@errorx666 I'd expect they would, unless any of the strings clash.
@RyanCavanaugh Could symbols be included in that addition? That'd be fairly useful as well. (They can't be in const enum
s, but they would make for safer JS interop.)
Should string enums still be generated with the reverse mapping? (value to key)
In some cases it's useful to be able to determine whether a value exists in the enum at runtime. You can currently do that by only looking at numeric values. If both keys and values are strings, I'm not sure how to accomplish that
I'd say that's a strong argument for not producing the reverse map. Object.keys
/ Object.values
would then produce a clean set of key names / values. Going from value -> key would be slightly awkward but I can't imagine why you'd need to do that.
Yeah, turning the reverse map off would be fantastic - or at least behind a flag
On Tue, Aug 30, 2016, 17:31 Ryan Cavanaugh notifications@github.com wrote:
I'd say that's a strong argument for not producing the reverse map. Object.keys / Object.values would then produce a clean set of key names / values. Going from value -> key would be slightly awkward but I can't imagine why you'd need to do that.
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/Microsoft/TypeScript/issues/1206#issuecomment-243623868, or mute the thread https://github.com/notifications/unsubscribe-auth/AAChnUl8PHgQ69whvb2xbzvHMkdzqOj2ks5qlMtngaJpZM4C9e4r .
I agree in the reverse mapping not existing for strings for that very reason. I don't usually need them anyways (I usually create the reverse mapping manually in JS, and I've rarely needed it in practice outside of pretty printing, which is also rare because of the type system).
On Tue, Aug 30, 2016, 20:39 Ian MacLeod notifications@github.com wrote:
Yeah, turning the reverse map off would be fantastic - or at least behind a flag
On Tue, Aug 30, 2016, 17:31 Ryan Cavanaugh notifications@github.com wrote:
I'd say that's a strong argument for not producing the reverse map. Object.keys / Object.values would then produce a clean set of key names / values. Going from value -> key would be slightly awkward but I can't imagine why you'd need to do that.
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub < https://github.com/Microsoft/TypeScript/issues/1206#issuecomment-243623868 , or mute the thread < https://github.com/notifications/unsubscribe-auth/AAChnUl8PHgQ69whvb2xbzvHMkdzqOj2ks5qlMtngaJpZM4C9e4r
.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/Microsoft/TypeScript/issues/1206#issuecomment-243625089, or mute the thread https://github.com/notifications/unsubscribe-auth/AERrBOMGFSmm-RMOGOybVTbyv2kBSgB6ks5qlM01gaJpZM4C9e4r .
@RyanCavanaugh Would I be able to use the enum as an indexer? E.g.
enum S: string {
A,
B = "X",
C
};
const foo = {} as { [ key: S ]: number };
It doesn't work if I use your "exactly equivalent" code. "An index signature parameter type must be 'string' or 'number'." (I suppose this actually applies to literal types in general.)
@errorx666 see #5683 for that
@RyanCavanaugh Any chance for this to land in 2.1.0 or is it further future ?
No idea. We just finished 2.0 and are still working out what's in the 2.1 roadmap.
@RyanCavanaugh Any chance this will end up on the roadmap now?
@RyanCavanaugh Does the idea of string enums (or enums of other primitive types) conflict with Typescript's design goals? Or is it just a matter of the right implementation?
@zspitz What about using this technique ? http://angularfirst.com/typescript-string-enums/
I'm reopening this issue, because it was closed with the move from codeplex, and doesn't seem to have been re-opened. https://typescript.codeplex.com/workitem/1217
I feel like this is very important for a scripting language.-- Especially for objects that are passed back and forth over web service calls.-- People generally don't use integer based enums in json objects that are sent over the wire, which limits the usefulness of the current enum implementation quite a bit.
I would like to re-propose the original contributor's suggestions verbatim.