The first way is quite simple and obvious. If the only thing that we want from enum is providing a set of values that we can compare with each other, we can write it as follows:
Seasons = {WINTER : 0, SPRING : 1, SUMMER : 2, AUTUMN : 3}Simple, isn't it? We just declare a new
Object
, that contains several properties - our enum constants. Now we can use it:commentSeason = (season) -> switch season when Seasons.WINTER console.log "The coldest season" when Seasons.SUMMER console.log "The hottest season" else console.log "The rainy season" commentSeason Seasons.WINTER commentSeason Seasons.SUMMER commentSeason Seasons.SPRING console.log "Are spring and autumn the same? %s", if (Seasons.SPRING == Seasons.AUTUMN) then "Yes" else "No"The output is:
The coldest season The hottest season The rainy season Are spring and autumn the same? NoWell, this variant is incredibly simple and will be enough for most of the situations when you decide to use enum in your code. But what should you do if you want to use some of these Java enum features like getting constant name, or finding constant by name, or using constructors for creating constants and adding methods to them? My solution is below.
Java enum constants are not numbers with names - they are instances of enum class they are declared in. And every enum is actually a class which you can add any number of additional methods and fields in and get all the constants in one array from. Quite comprehensive thing, to be honest.
That's why I tried to write a new class that will be parent for all other enums in CoffeeScript. Let's first look at the sample of its child:
class Seasons extends Enum @size: 0 @_VALUES: {@WINTER, @SPRING, @SUMMER, @AUTUMN} @WINTER: new (class extends Seasons comment: () -> console.log "%s is the coldest season.", @name() )(-8) @SPRING: new (class extends Seasons comment: () -> console.log "%s is rainy season.", @name() )(3) @SUMMER: new (class extends Seasons comment: () -> console.log "%s is the hottest season.", @name() )(15) @AUTUMN: new (class extends Seasons comment: () -> console.log "%s is rainy season.", @name() )(2) averageTemperature: undefined constructor: (@averageTemperature) -> super getTemperature: () -> if @averageTemperature > 0 return @averageTemperature + " degrees above zero" else if @averageTemperature == 0 return "zero" else return @averageTemperature + " degrees below zero" getAllValues: () -> return @getSuperclass().values()First of all, give a special attention to lines 2 and 3. They are both absolutely necessary in child enum class. The
@size
field will hold the number of constants after their declaration and must be initialized by 0 before any other code. The @_VALUES
field should contain the pre-declarations of enum constants. In fact, all this values are undefined before next lines, but they reserve names for your constants.Then look at line 27, where the
constructor
is declared. You will not need it if you have no additional fields, but if you do, you should necessarily call super
constructor (line 28). Below the constructor there are some more methods that every constant will have.Coming back to top, look at the way how each constant is declared (e.g. lines 5-8) - it is static field of enum class, which is the instance of anonymous class inherited from
Seasons
. In our case this class has additional method comment
, that prints out some text in stdout
.Now we have very powerful enum
Seasons
and can do a lot of things with it. We can get name
and ordinal
of each constant:season1 = Seasons.WINTER console.log "First season info: name: %s, ordinal: %s, t_avr: %s", season1.name(), season1.ordinal(), season1.getTemperature() season1.comment() season2 = Seasons.SUMMER console.log "First season info: name: %s, ordinal: %s, t_avr: %s", season2.name(), season2.ordinal(), season2.getTemperature() season2.comment()The output is:
First season info: name: WINTER, ordinal: 0, t_avr: -8 degrees below zero WINTER is the coldest season. First season info: name: SUMMER, ordinal: 2, t_avr: 15 degrees above zero SUMMER is the hottest season.Then, we can get all the values from enum class or even from one of its constants and get constant by name:
console.log "All values from enum class: %s", Seasons.values() console.log "All values from enum const: %s", season1.getAllValues() console.log "Enum const from name \"SPRING\": %s", Seasons.valueOf("SPRING")The output is:
All values from enum class: WINTER(0),SPRING(1),SUMMER(2),AUTUMN(3) All values from enum const: WINTER(0),SPRING(1),SUMMER(2),AUTUMN(3) Enum const from name "SPRING": SPRING(1)We can also compare constants and use them in
switch
:console.log "WINTER equals SUMMER? %s", if season1 == season2 then "Yes" else "No" console.log "WINTER less than SUMMER? %s", if season1.compareTo(season2) < 0 then "Yes" else "No" switch season1 when Seasons.WINTER console.log "Winter is passed to switch" when Seasons.SUMMER console.log "Summer is passed to switch" else console.log "Some other season is passed to switch"The output is:
WINTER equals SUMMER? No WINTER less than SUMMER? Yes Winter is passed to switchI'm not sure but these seem to be the basic usages of enums even in Java.
The only thing that I have not told about yet - the Enum class itself. It's source code is the following:
class Enum @_size: 0 @_VALUES: {} @values: () -> values = new Array() for value in Object.keys(@_VALUES) values.push @_VALUES[value] return values @valueOf: (name) -> return @_VALUES[name] _name: undefined _ordinal: undefined constructor: () -> Class = @getSuperclass() @_name = Object.keys(Class._VALUES)[Class._size] @_ordinal = Class._size Class._size += 1 Class._VALUES[@_name] = this name: () -> return @_name ordinal: () -> return @_ordinal compareTo: (other) -> return @_ordinal - other._ordinal equals: (other) -> return this == other toString: () -> return @_name + "(" + @_ordinal + ")" getClass: () -> return this.constructor getSuperclass: () -> return @getClass().__super__.constructor exports.Enum = EnumI suppose that it doesn't need much explanations, but I do want to comment out the constructor (lines 17-22), as it is very important. In fact, the constructor is called in anonymous class. That is why for each constant
getSuperclass()
will return enum class and getClass()
- one of anonymous classes. Thus Class
variable will be Seasons
in our example.What we want to do is to store in
Seasons._VALUES
references to our constants after all these constants are declared. At the same time, we need some place where we can get the name of each constant in its constructor from. That is why in Seasons
class we pre-declared constants (look at line 3 of Seasons
source code). Note that you should declare them in the same order as you did in Class._VALUES
, otherwise you will get wrong names. Now, in line 19 we get all the keys of Class._VALUES
(i.e. an array with 'WINTER'
, 'SPRING'
etc) and get the key at position equal to its ordinal. At the end of constructor we redefine Class._VALUES[@_name]
: it was equal to undefined
before and now get reference to this instance.Well, I thing that is all for now. I'm not sure that my solution is the most optimal and elegant. I'll be glad to know your opinion and to discuss any other variants of creating Java-like enums in CoffeeScript.
To be continued...
No comments:
Post a Comment