Introduction
Most languages build their enum for one shape: a small, fixed set of named constants. That covers DayOfWeek fine, but it falls apart the moment a variant needs its own data attached, which is the sum-type shape we covered a few posts back in algebraic data types. Scala 3's enum is deliberately built to cover both cases with one keyword.
Python: fine for constants only
Python's Enum handles the constant-list case cleanly. The moment a variant needs to carry a different data type per case, you are back to the sum-type problem, and Enum has nothing to offer.
class Direction(Enum):
NORTH = "N"
SOUTH = "S"
EAST = "E"
WEST = "W"
# Clean, for a fixed list of interchangeable constants.
# But "a shape carrying different data per case" isn't something
# Enum does: that goes back to the class-hierarchy workaround from
# the ADT post, Enum here doesn't help at all.TypeScript: real footguns
TypeScript's enum has documented pitfalls that go past ergonomics. Numeric enums are mapped in both directions at runtime, so the compiled object holds the name and the reverse numeric lookup, which surprises anyone expecting a plain object. And const enum inlines values at compile time in a way that breaks across separately compiled module boundaries, a known trap when you are building a library other TypeScript projects consume.
enum Status { Approved, Declined }
console.log(Status.Approved); // 0
console.log(Status[0]); // "Approved": a reverse mapping that
// most developers don't expect exists.
const enum Fast { On, Off }
// Inlined at every use site at compile time: great for bundle size,
// but famously breaks if the enum's declaration and its usage end up
// compiled by different tools/passes, since there's no runtime object
// left to look values up in at all.The community workaround, string-literal union types instead of enum, is safer. Which leaves TypeScript with a built-in enum feature that plenty of style guides tell you to avoid.
Java: verbose once a case needs its own behaviour
Java enums carry constructor arguments and even per-constant method bodies, which is more than Python offers. The syntax for per-case behaviour is heavier than declaring a separate class, though, and an enum mixing plain constants with cases that need their own logic gets noisy fast.
enum Shape {
CIRCLE {
double area(double r) { return Math.PI * r * r; }
},
SQUARE {
double area(double r) { return r * r; }
};
abstract double area(double r);
}
// Workable, but every case with distinct behavior needs its own
// anonymous-class-style body, and this scales poorly past two or three
// cases with real per-case logic.Scala: one keyword, both shapes
The direct translation of the Python Direction above, same string codes and all:
enum Direction(val code: String) {
case North extends Direction("N")
case South extends Direction("S")
case East extends Direction("E")
case West extends Direction("W")
}
Direction.North.code // "N": the value, exactly like Python's NORTH = "N"
Direction.North.ordinal // 0: a predictable, ordinal index.There is more to type than in the Python version. What you get for it: adding a case that carries its own parameter is one more line. No rewrite into a dedicated ADT, and no giving up on Direction being a single type, which is what Python's Enum would force. Say you want a Direction expressed in degrees:
enum Direction(val code: String) {
case North extends Direction("N")
// ... omitted for brevity
case Heading(degrees: Double) extends Direction("H")
}
// This IS the "sealed trait plus case classes" pattern from the ADT
// post: 'enum' is sugar over exactly that, with the same
// compiler-checked exhaustiveness in a match:
def describe(direction: Direction): String = direction match {
case Direction.North => "due north"
case Direction.South => "due south"
case Direction.East => "due east"
case Direction.West => "due west"
case Direction.Heading(degrees) => s"heading at $degrees degrees"
}That is the whole point. Scala's enum is not a smaller construct parked next to sum types. It is the same mechanism, a sealed hierarchy with compiler-checked exhaustiveness, wearing shorter syntax for the common case where every variant lives under one name.
One mental model instead of two
Because Python and Java both need a different construct once a case needs its own data (dropping Enum for a class hierarchy, or writing per-constant method bodies), an AI agent has to correctly judge, up front, which construct a new requirement calls for, and get that judgment right before writing any code. Guess wrong, model something as a plain Enum that later needs per-case data, and the whole thing needs restructuring.
Scala's enum covers both under one keyword, so an agent extending an existing enum with a case that now needs a parameter never has to switch data structures. Same declaration, one case gains a field.
