enumeratum
enumeratum copied to clipboard
Ability to opt out of "unsafe" methods
Enum
gives you withName
which throws, whether you like it or not. It also gives you "safe" versions but it discourages you from using them because they have longer names.
It would be nice to be able to make the opposite choice: to have only the safe methods. E.g.
def withName(name: String): Try[A]
and variants. Or Option
not Try
.
People who really want to "force" the Try
could call Foo.withName("bar").get
which would be visible to e.g. wartremover in user code.
It also gives you "safe" versions but it discourages you from using them because they have longer names.
Yeah, this is a good point, but changing the API would:
- Break compatibility w/ std lib
Enumeration
. Right you just need to change the declaration of your std libEnumeration
and all your call sites stay the same - Break backwards compatibility.
That said, I'm not totally opposed to doing this and in fact would like to, while mitigating the effects of breaking changes.
Currently playing around with the idea of introducing an UnsafeEnum
trait that mirrors current behaviour and evolving Enum
do what you mention.
Let's brainstorm a few more ideas :)
stdlib Enumeration
compatibility is something I hadn't appreciated was important, so I'll have to defer to you if my suggestion is unworkable from that point of view. Do I need it without realising it? I think I'm just calling methods on Enum
currently as if it was any other standalone userland trait.
Backwards compatibility is definitely important though. If I had a time machine, I might try to summon enough arrogance to argue for changing the interface in the past to what I want now, but that's not what I'm suggesting. The reason I say "opt out" is so that people can keep the API as is (if they actively want to use the throwing methods) or migrate at their leisure if they want to - even one enum at a time.
I'm thinking something like this:
trait SafeEnumApiViaOption[A <: EnumEntry] {
def withName(name: String): Option[A]
// other Option returning methods etc but nothing throwing
}
type SafeEnum[A] = SafeEnumApiViaOption[A]
trait UnsafeStdlibCompatApi[A <: EnumEntry] {
@SuppressWarnings(/*wartremover*/)
def withName(name: String): A
// ... everything there is now in the public API, safe and unsafe
}
type Enum[A] = UnsafeStdlibCompatApi[A]
In userland, people can then choose to extend Enum
(as they do now and to avoid breaking existing code) or extend SafeEnum
.
The indirection through type aliases (and the trait names I've written) are for illustration - you probably wouldn't want that in real life. At this point, I'm leaving open the possibility of letting users have the equivalent of SafeEnumApiViaTry[A]
, SafeEnumApiViaEither[A]
but if that's even a good idea, it would probably be done a different way (e.g. extension trait either in the library or by users).
Clearly you couldn't mix both Enum
and SafeEnum
together (either for code deduplication in the implementations or in userland) to get "both APIs" because they define same-name methods returning different types. That's sad, but unavoidable I think. If we wanted code deduplication in the implementation, we could only do it via forwarders (which may or may not be worthwhile).
All that said, if you actually are up for evolving the existing Enum
trait in a non-backwards compatible way, I'm not going to argue against it...
Ah, that sounds like the reverse of what I proposed, which is cool too.
Either way, if we introduce a new trait for enums, we would need to figure out how to evolve the integrations too (which are mostly typed to Enum
). Perhaps we will need to introduce a typeclass or something that both Enum
and SafeEnum
are part of and have instances for both in order to prevent duplication...