wren
wren copied to clipboard
[RFC] Enums
Idea for Enums in Wren:
Use #!enum
attribute. This will tell the compiler that if there are no braces after an identifier, considers its value as the identifier name. All properties would be static.
#!enum
class CLanguageStandard {
c89 // same as static c89 { "c89" }
c90
c99
c11
c17
c18
c2x
gnu89
gnu90
gnu99
gnu11
gnu17
gnu18
gnu2x
iso9899_1990 {"iso9899:1990"}
iso9899_199409 {"iso9899:199409"}
iso9899_1999 {"iso9899:1999"}
iso9899_2011 {"iso9899:2011"}
iso9899_2017 {"iso9899:2017"}
iso9899_2018 {"iso9899:2018"}
}
I'd love to explore something like this with a pre-processor. :-) This could be easily done WITHOUT support in core Wren.
At present I create enums dynamically using Meta.compile. So I'd do something like this:
import "./dynamic" for Enum
var Direction = Enum.create("Direction", ["north", "south", "east", "west"])
var dir = Direction.east
System.print(dir) // 2
This works well enough but I think enums are important enough to be included in the language proper even if behind the scenes the compiler just converts them to a class with a bunch of static read-only properties.
Most languages seem to have them in some shape or form though I've always found Go's take on them very peculiar!
For incrementaly numeric values. If no braces are found after an identifier and a numeric value is found in the #!enum
attribute. Take that as the starter value for a serial number.
#!enum = 1
class Fingers {
// incrementaly store the values
thumb // same as static thumb { 1 }
index // same as static index { 2 }
middle // same as static middle { 3 }
ring // same as static ring { 4 }
pinky // same as static pinky { 5 }
}
If a step is needed for each value, a group in enum can be used to specify it.
#!enum(
start = 0,
step = 2)
I have an option in my dynamic Enum to start from a different value than 0 though I didn't bother to add a step as I couldn't think of any obvious use cases.
However, I do have a Flags enum where successive values are powers of two, starting from 2^0 = 1.
The other option is create a class like https://github.com/spatie/enum and use the Meta way like PureFox48 described
My vote would be a breaking change to introduce a new reserved keyword for "enum":
enum State {
nothing // default to 0
active // default to 1
inactive // default to 2
undetermined { 666 }
error // 667
}
enum Math {
pi { 3.141592 }
e { 2.718281 }
goldratio { 1.618033 }
}
enum Company {
ceo { "Gauss" }
cto { "Eurel" }
cfo { "Nostradamus" }
}
enum Mixed {
one { Hello World }
two { 3.1415 }
three { 666 }
four { true }
}
Matthew proposition is not realistic. It opens to much edge cases and requires the compiler to understand/remember enum values. What is the default value of an enum of String or any other type. If enums values are evaluated, there is a possibility that theses values are not constants. At minimum, the grammar should be at minimum changed to EnumEntry = ValueComputation, to convey the idea the value is computed only once.
In this recent Hacker News item, there was some reasoned criticism of Wren on a couple of fronts.
The point about binary operators not being symmetric in Wren was discussed at length in #793 and is not an easy problem to solve.
However, I think we could address the absence of enums fairly easily as long as we keep it simple - just restrict it to consecutive integer values which probably covers at least 90% of use cases for enums in C.
For syntax I'd suggest
enum Direction(0) { // the 'start' parameter could be any constant integer
north
east
south
west
}
On seeing that, the compiler would treat it is as though it had been written:
class Direction {
static start { __start = 0 }
static north { __start }
static east { __start + 1 }
static south { __start + 2 }
static west { __start + 3 }
}
and generate bytecode accordingly.
The 'start' parameter would be optional and, if omitted, would be given a default value of 0.
We might need to think up a different name for 'start' so it wasn't likely to clash with the names chosen for the enum constants themselves.
All the other 'enum' cases envisioned by @mwasplund could be dealt with almost as easily by manually expressing them as classes with read-only static properties,
Matthew proposition is not realistic. It opens to much edge cases and requires the compiler to understand/remember enum values. What is the default value of an enum of String or any other type. If enums values are evaluated, there is a possibility that theses values are not constants. At minimum, the grammar should be at minimum changed to EnumEntry = ValueComputation, to convey the idea the value is computed only once.
My examples were meant to be very broad. At its core I was calling out the enum
keyword as opposed to a shebang mutation of existing class
definitions. I am fine if the underlying type has to be an integer to simplify the implementation. Could you not require that the enum value getter with initial values was implemented using a constant value? After that it seems like my sample could be syntactic sugar for a constant integer value in the bytecode generated. But I defer to the language experts.
It all depends on the flexibility and constraints you are willing to impose on your enum implementation. The compiler is a dumb simple pass compiler, so what you are referring as no real meaning in our case. We can restrict enums to be integer only (though there are no such thing as integer in the VM). But people raised that it should be more general more like a named constant dictionary. We may need to allow to provide a default value generator, and possibly other fancy stuff like String conversions. So it is not as easy as do what the others do, though we could restrict ourselves to do so.
As XMAS TV is crap as usual, I've just written some code to generate a group of related constants of any type dynamically and illustrated how this could be used for @mwasplund's examples.
import "meta" for Meta
/* Group creates a group of related named constants of any type.
The group has static property getters for each member.
*/
class Group {
// Creates a class for the Group (with an underscore after the name) and
// returns a reference to it.
static create(name, members, values) {
if (name.type != String || name == "") Fiber.abort("Name must be a non-empty string.")
if (members.isEmpty) Fiber.abort("A group must have at least one member.")
if (members.count != values.count) Fiber.abort("There must be as many values as members.")
name = name + "_"
var s = "class %(name) {\n"
for (i in 0...members.count) {
var m = members[i]
if (values[i] is String) {
s = s + " static %(m) { \"%(values[i])\" }\n"
} else {
s = s + " static %(m) { %(values[i]) }\n"
}
}
s = s + "}\n"
s = s + "return %(name)"
return Meta.compile(s).call()
}
}
var State = Group.create("State", ["nothing", "active", "inactive", "undetermined", "error"],
[0, 1, 2, 666, 667])
System.print(State.error) //> 667
var Math = Group.create("Math", ["pi", "e", "goldratio"], [3.141592, 2.718281, 1.618033])
System.print(Math.e) //> 2.718281
var Company = Group.create("Company", ["ceo", "cto", "cfo"], ["Gauss", "Eurel", "Nostradamus"])
System.print(Company.ceo) //> Gauss
var Mixed = Group.create("Mixed", ["one", "two", "three", "four"], ["Hello World", 3.1415, 666, true])
System.print(Mixed.one) //> Hello World
System.print(Mixed.four) //> true
This, of course, is different from just using a map as the resulting class is immutable and the property values are as near to constants as Wren can get.
Although C-style enums can also be generated dynamically, I still think it would be worthwhile for these to be included in the language proper.