Having watched some of the SPP meeting (thanks @mdedetrich and others who spoke on behalf of the lib … sadly I can’t attend them myself), my fear is that people don’t fully understand what core problems Enumeratum solves, but are at the same time jumping to discussing alternative technical solutions.
Yes, it boils down to “deserialising to sum types”, but there are subtle real-world requirements/patterns to Enums that shouldn’t be overlooked, otherwise we risk providing users with something half-baked, and they end up disappointed and going elsewhere anyways.
So, here they are in point form as functional requirements, but in specific, I’d like to focus on 2: (bolded)
- Allow for a type-safe enum
- Ability to deserialise String values into the types of an ADT
- This requires the ability to conjure a collection of your ADT types.
- Ability to guarantee declaration ordering
- Deserialising from non-String values
- Compile-time checks for value uniqueness
I think by now, many people are convinced that the first and second are worth solving. Arbitrary-ordering is also a fairly common requirement (ever need to provide some kind of list in a UI with arbitrary ordering? like…country, or gender…), and being forced to assign values to each member in code increases surface area for errors. And deserialising non-String values…well, anyone who has the good fortune of often working with legacy codebases, legacy databases, web APIs, or any situation where I/O bandwidth or performance is a concern would find this helpful.
BTW, if we recognise the need for 4 (deserialising from non-Strings), we must solve 4.1(compile-time checks for value uniqueness), because we can no longer assume the compiler will do identifier-based uniqueness checks for us. Otherwise, we get in to this unfortunate situation
sealed abstract class HttpStatus(val value: Int)
object HttpStatus {
// MyGenericValueEnum is an alternative sum-type list-maker summoner that is not
// Enumeratum.
//
// I assume nobody wants to create and instantiate these lists multiple times in their
// code everywhere they deserialise (e.g. from JSON, DB, w/e, Query params),
// so we'll hold a stable list in the companion object
val values = MyGenericValueEnum[HttpStatus]
object Ok extends HttpStatus(200)
object Created extends HttpStatus(200)
// etc
object Forbidden extends HttpStatus(403)
object NotFound extends HttpStatus(403) // <--- this sort of thing should not compile
// ...
}
// clearly we can do better
I believe that in order to solve all the requirements, the implementation must be specialised. In other words, you would necessarily end up writing a custom macro (or you add to the language/compiler directly.), as opposed to building on top of something like Shapeless’s Generic, because (and I’m glad to be proven wrong), you can’t. On top of that, there is the issue of emitting nice, relevant compiler error messages …
For what it’s worth, Enumeratum has been around for years, solving a very specific problem in an extensive way, even before it was reliable or feasible for other libraries. It’s got high adoption, has been vetted by 3rd parties (1, 2, 3), and has solid integrations with popular libraries.
These qualities, plus the fact that it solves the aforementioned core problems, means that offers a “batteries-included” experience for beginners, and yet scales to the needs of advanced/niche users, making it a good fit for the Scala Platform (bias warning )