enumeratum icon indicating copy to clipboard operation
enumeratum copied to clipboard

Ability to opt out of "unsafe" methods

Open pmpfr opened this issue 7 years ago • 3 comments

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.

pmpfr avatar Jan 03 '18 16:01 pmpfr

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 lib Enumeration 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 :)

lloydmeta avatar Jan 04 '18 07:01 lloydmeta

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...

pmpfr avatar Jan 04 '18 10:01 pmpfr

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...

lloydmeta avatar Jan 11 '18 00:01 lloydmeta