I kinda hate enums (in languages that have them) compared to Lisp symbols.
Let’s say I need to refer to the compass directions or the names of
planets. In Lisp, I just can. '(north north west north)
or whatever.
With enums, I have to actually redundantly type out all of them before I use them. And there, type them again. The human compiler at work.
If you have syntax macros, code traversal, or even just quasiquoting, you are gonna need symbols anyway. And using them in the language itself, as Lisp does, can be an elegant way to make the definition of the language itself use fewer layers and be simpler.
The selling point of enums is using compiler errors to catch bugs. Same reason some peeps like types.
My take is that is an historical artifact. Types were used for performance (before type inferring compilers existed) e.g. to have a simpler (or no) runtime.
If you want error checking or linting, that’s awesome, but don’t rely on this onion in the varnish to make us do the redundant “are you sure you really mean ‘souht’ and ‘south’ to be different?” Having the linter output all symbols grouped by use, and maybe with Levenshtein checking, is a better use of the programmer’s time. Have the IDE or the inferrer help with that.
There’s so much faux work in these boiler plate languages. Like, imagine I was writing a novel and I had to predeclare every character or location instead of being able to just right click on them after I’ve actually used them in the story, and then add them to the spell checker.
I have another beef with enums, but that’s secondary. It’s that the giant chunks of case switches isn’t really the most elegant pattern. I use it occasionally when anything else is overkill, but what you ideally wanna use is just methods.
Return foo.bar()
Instead of
if foo=baz return 78 else if foo=quux return (87);
Especially if foo can be many things.
Again, this sort of member/method structure can be overkill if there
are only a handful of simple symbols, chars or ints to dispatch
between, but so is enum (overkill, that is).
(Of course I forgive enums when they are used for compatibility with C purposes.)
Simultaneously as I was writing the above, Simon (my friend♥︎) emailed me this example from The Swift Programming Language
enum TriStateSwitch {
case off, low, high
mutating func next() {
switch self {
case .off:
self = .low
case .low:
self = .high
case .high:
self = .off
}
}
}
var ovenLight = TriStateSwitch.low
ovenLight.next()
// ovenLight is now equal to .high
ovenLight.next()
// ovenLight is now equal to .off
An example of both problems.
First, the “having to predeclare”, which in this case is relatively minor. In Lisp we’d only be getting rid of the single line:
case off, low, high
Second, the proclivity of enums to show up with a bunch of switch cases, glorified if-statements basically.
Do we really want a state machine where every state is, let’s face it, probably cut-and-pasted and edited from the others? Talk about error prone!
Here is one take on this same functionality in Lisp:
(define ovenlight
(let ((states (circular-list 'low 'high 'off)))
(lambda (next)
(set! states (next states))
(car states))))
I especially likes that it defines a data structure that is the sequence of lights—I can easily edit the sequence without having to infer the sequence from the state machine and carefully edit each cases.
And to use it:
(ovenlight identity); → returns low
(ovenlight cdr); → returns high
(ovenlight identity); → returns high
(ovenlight cdr); → returns off
(ovenlight identity); → returns off
Or to jump to a particular value, for example high
:
(ovenlight (cut memv 'high <>))
Or you can change it to any order, dynamically. Here is an example order that would not be possible with a state machine:
(ovenlight (constantly (circular-list 'high 'low 'high 'low 'off)))