waneck / testrepo

0 stars 0 forks source link

Issue 923 - Feature request: Enums as sets of strings - haxe #923

Open waneck opened 11 years ago

waneck commented 11 years ago

[Google Issue #923 : http://code.google.com/haxe/issues/detail?id=923] by jdonald...@gmail.com, at 2012-06-13T13:16:36.000Z Sometimes various js targets accept or produce a constrained set of strings. For instance, "errorThrown" for ajax: http://api.jquery.com/jQuery.ajax/

@:fakeEnum gets me half of the way there, since it can use simple string checks for enum values. However, it still produces an enum object instead of a simple string if you're not externing an existing object.

Here's a related message board post: http://haxe.org/forum/thread/3395#nabble-td6612576

I came up with a hack for this by creating an "Init" class that creates the values that I want, and then an enum that externs this class. E.g.

class Test { public static function main() { var b = Foo.BAR; switch(b){ case BAR : trace('bar!'); case BAZ : trace('baz!'); } } }

@:fakeEnum(String) @:native("_Test.InitFoo") extern enum Foo { BAR; BAZ; }

private class InitFoo{ static var BAR = 'BAR'; static var BAZ = 'BAZ'; }

This method almost behaves the way I'd want it to. Using @:fakeEnum seems to remove the compiler check that all enum states must be handled. It would be nice to keep this check. Also, this method is very flakey... I have to provide each enum state in two places without the compiler checking that I get it right, and I have to occasionally use undocumented private class path behavior for @:native access.

It would be nice to get another metadata flag here to make a class behave as an enum. I don't have a particularly elegant solution or name in mind, perhaps @:mockEnum. This would let you name the enum states however you wanted, and provide their values however you wanted. It seems possible to even use "inline" in this case. I'm not sure if using "class" semantics in this way is best though.

I imagine it would look like this:

@:mockEnum class Baz{ static var BOO = 'BOO'; static var BING = 'BING'; }

waneck commented 11 years ago

[comment from simon.kr...@simn.de, published at 2012-06-13T13:45:03.000Z] I don't think this works with classes. In above example, "b" is simply a String and the switch doesn't know what to do with it other than checking its value against some constructors. It would seem more natural to still use an enum as base, but employ a proper exhaustion check and generate string values instead of enum values in the typed AST.

waneck commented 11 years ago

[comment from simon.kr...@simn.de, published at 2012-06-13T16:23:39.000Z] I have found a concise, albeit tricky solution for this by using @:StringEnum enum { ... } and implying both extern and @:native("String"). Might be I'm missing a more obvious solution, so please review attached.

waneck commented 11 years ago

[comment from ncanna...@gmail.com, published at 2012-06-13T16:34:46.000Z] Looks ok for the principle. I'm not sure about the naming.

We have @:fakeEnum(String) which express a somehow similar concept, but has quite a different implementation since the enum is extern.

Should we use the same naming and make the difference based on the declared "extern" enum status, or should we use another specific name as you did ?

waneck commented 11 years ago

[comment from simon.kr...@simn.de, published at 2012-06-13T17:05:47.000Z] That's largely a matter of taste. Personally I don't like reading "fake" in my code because it has a negative connotation in my opinion, but either metadata label is fine with me.

Changing the behavior depending on extern doesn't sound like a good idea. The extern declaration is just a necessity so the String type is not overwritten on some platforms, so giving it that kind of influence seems rather cryptic.

I wonder if the string usage could be considered an optimization and be the default behavior for @:fakeEnum(String) when --no-opt is not in place. But I suppose that could lead to surprises too.

waneck commented 11 years ago

[comment from jdonald...@gmail.com, published at 2012-06-13T17:49:51.000Z] I was a bit shortsighted when I wrote the title to this request, since these type of "class constructed" enums could be any value type. If they were integer, you could do some useful classes like:

@:ValueEnum class HttpStatus{ public static var NotFound = 404; public static var MethodNotAllowed = 405; ... }

This makes haxe enums behave more like enums I've used in other languages. But, it abuses class semantics a bit. Would it be out of the question to introduce a new haxe entity/keyword? Like:

inline_enum HttpStatusEtc = { NotFound = 404; MethodNotAllowed = 405; YouCanUseStrings = 'of course'; AndBooleans = true; ... }

These would be a subset of class semantics... all fields are public, static, and inline, so everything would be defined at compile time. The benefit of this is that the enum wouldn't need to be defined at all in the run time... the compiler could just insert and check enum values, and ensure that all enum states are covered.

The inline_enum name is bad, there's something better I'm sure. I'm also out of my league here with the other compiler considerations, so I leave it to the compiler gurus :)

Thanks for considering this though!

waneck commented 11 years ago

[comment from simon.kr...@simn.de, published at 2012-06-13T18:19:18.000Z] We could support it by abusing metadata:

@:IntEnum enum HttpStatus { @:v(404) NotFound; @:v(405) MethodNotAllowed; }

waneck commented 11 years ago

[comment from simon.kr...@simn.de, published at 2012-06-14T21:15:49.000Z]

waneck commented 11 years ago

[comment from andy@onthewings.net, published at 2012-07-23T10:36:43.000Z] I found a way to provide values for fakeEnum, that is working correctly on Flash/JS/Neko/PHP. https://gist.github.com/3162981

Cpp/Java/C# targets gives wrong output that gives error when compile. It would be nice to simply fix those targets and include this into the haxe compiler unit test :)

waneck commented 11 years ago

[comment from ncanna...@gmail.com, published at 2012-07-23T10:39:50.000Z] Note for target implementors : it is necessary in that case to handle :fakeEnum as a type-substitution (see http://code.google.com/p/haxe/source/browse/trunk/genswf9.ml#218)

waneck commented 11 years ago

[comment from si...@haxe.org, published at 2012-11-17T08:40:43.000Z] Something we could do is allow @:native(Int | String) on argument-less enum constructors. This would lead to the following monster:

@:fakeEnum @:native("String") enum StageAlign { @:native("Bottom") Bottom; @:native("Top") Top; }

That wins no beauty contest, but it would be a working internal representation that requires no core changes other than the attached optimization.

waneck commented 11 years ago

[comment from ncanna...@gmail.com, published at 2012-11-18T15:18:00.000Z] @Simon : seems fine, but we might prefer using the Flash syntax :

@:fakeEnum("String") enum StageAlign { }

Also, @:native is meant for renaming, maybe @:value is more correct here ?

waneck commented 11 years ago

[comment from si...@haxe.org, published at 2012-11-18T15:44:57.000Z] So how far should we take it? It's a parser one-liner to allow const assignment to argument-less enum constructors and store it in the metadata, and with that we could infer @:fakeEnum(T) in Typeload.

enum StageAlign { Bottom = "bar"; Top = "foo"; }

That looks slightly nicer than the previous thing.

waneck commented 11 years ago

[comment from ncanna...@gmail.com, published at 2012-12-02T19:32:56.000Z] Looking at it I came up with a more complete patch

However I'm not satisfied, in particular because it requires the code geenerators to handle one more specific case which are enums renamed to String / Int

A more general solution would be to use abstract/opaques types which would define a new type but still allows to inline String values directly. The only loss compared to enums is that it's not possible to get check complete matching of all constructors in switch, but that seams like a good trade off.

I'm merging with abstract types issue then.

waneck commented 11 years ago

[comment from si...@haxe.org, published at 2013-02-11T18:27:36.000Z] I'm reopening this because I don't think this can be done in a reasonable way even with current abstracts. While we can easily define an abstract HttpStatus(Int) with @:from and @:to functions for a separate (real) enum, the mapping from that enum to the Int remains a runtime thing.

I still think we should support this properly in the syntax, but be pragmatic about the typing part and just pass the values through in the @:value metadata.

waneck commented 11 years ago

[comment from si...@haxe.org, published at 2013-02-11T19:07:15.000Z] inline enum HttpStatus { NotFound = 404; MethodNotAllowed = 405; }

This is hands down the best syntax: concise, easy to parse, nice to read and almost self-explanatory.

waneck commented 11 years ago

[comment from back2dos@gmail.com, published at 2013-02-11T19:49:56.000Z] I like it!

I know this is not a primary use case scenario, but how would one convert an Int to a HttpStatus?

Also, I'm curious to know how this will be represented at compile time / from macro perspective (muahahaha), especially since EnumField seems unable to carry an expression.

waneck commented 11 years ago

[comment from heinz.ho...@googlemail.com, published at 2013-02-18T02:15:03.000Z] It looks really nice, a pleasure to use in conjunction with external libraries ;)

waneck commented 11 years ago

[comment from jdonald...@gmail.com, published at 2013-02-18T02:56:57.000Z] The inline enum syntax seems good for the proposal, but does it make sense for the original Haxe syntax?

e.g. What does this look like in the runtime?

inline enum Foo { Bar; Baz; }

waneck commented 11 years ago

[comment from si...@haxe.org, published at 2013-02-19T10:03:59.000Z]

waneck commented 11 years ago

[comment from si...@haxe.org, published at 2013-02-24T17:12:14.000Z]