Thanks jvican,
I’d like to formally submit a proposal to have Enumeratum-core added to the SP.
#Introduction
Enumeratum is an enumerations library. Its goal is to make it easy to declare ADTs and use them as enumerations that can be deserialised from primitive values or Strings. Unlike Enumeration
in the standard library, it is type safe.
Other highlights include:
- Zero dependencies (Macros were written with no other dependencies like macro-paradise, or compatibility-shim libs) unless if you count
scala-reflect
. - Performant: Faster than
Enumeration
in the standard library (see benchmarks) - Idiomatic: you’re very clearly still writing Scala, and no funny colours in your IDE means less cognitive overhead for your team
- Simplicity; most of the complexity in this lib is in its macro, and the macro is fairly simple conceptually
- No usage of reflection at runtime. This may also help with performance but it means Enumeratum is compatible with ScalaJS and other environments where reflection is a best effort (such as Android)
- Comprehensive automated testing
- Integration with popular libraries like Play, Circe, ReactiveMongo, Argonaut, and UPickle
As for which parts to put into the SP, I think starting small makes it easier to keep the discussion going, so maybe the following core functionality would be nice:
- The normal
Enum[A <: EnumEntry]
- The
ValueEnum
s
The macros library will also need to be included because core depends on it.
#Example usage
Enum
scala> import enumeratum._
scala> sealed trait DummyEnum extends EnumEntry
scala> object DummyEnum extends Enum[DummyEnum] {
| val values = findValues
| case object Hello extends DummyEnum
| case object GoodBye extends DummyEnum
| case object Hi extends DummyEnum
| }
scala> DummyEnum.withNameOption("Hello")
res0: Option[DummyEnum] = Some(Hello)
scala> DummyEnum.withNameOption("Nope")
res1: Option[DummyEnum] = None
ValueEnum
import enumeratum.values._
sealed abstract class LibraryItem(val value: Int, val name: String) extends IntEnumEntry
case object LibraryItem extends IntEnum[LibraryItem] {
case object Book extends LibraryItem(value = 1, name = "book")
case object Movie extends LibraryItem(name = "movie", value = 2)
case object Magazine extends LibraryItem(3, "magazine")
case object CD extends LibraryItem(4, name = "cd")
// case object Newspaper extends LibraryItem(4, name = "newspaper") <-- will fail to compile because the value 4 is shared
/*
val five = 5
case object Article extends LibraryItem(five, name = "article") <-- will fail to compile because the value is not a literal
*/
val values = findValues
}
assert(LibraryItem.withValue(1) == LibraryItem.Book)
LibraryItem.withValue(10) // => java.util.NoSuchElementException:
Design
As much as possible, Enum
's API was written to look like Enumeration
to facilitate smooth migration and adoption. Since then various contributors have added functionality such as nice string transforms for the names of the enum members (see mixins usage).
ValueEnum
was written to solve the problem of not having compile-time uniqueness checks on Enum values and to be able to deserialise primitive values to Enum members.
Attention was also paid to small things like making sure the usage was natural in IntelliJ (no funny colours) and that the macros expanded into code that doesn’t bother WartRemover.
Implementation
Uses blackbox macros. ASTs are built manually w/o quasiquotes to kill off another dependency for 2.10 users.
Benchmarks
Details on the benchmarking setup are here.
Benchmarks for Scala version 2.11.8 on MBP 2013 running OSX El Capitan with JDK8:
[info] Benchmark Mode Cnt Score Error Units
[info] EnumBenchmarks.indexOf avgt 30 11.628 ± 0.190 ns/op
[info] EnumBenchmarks.withNameDoesNotExist avgt 30 1809.194 ± 33.113 ns/op
[info] EnumBenchmarks.withNameExists avgt 30 13.540 ± 0.374 ns/op
[info] EnumBenchmarks.withNameOptionDoesNotExist avgt 30 5.999 ± 0.037 ns/op
[info] EnumBenchmarks.withNameOptionExists avgt 30 9.662 ± 0.232 ns/op
[info] StdLibEnumBenchmarks.withNameDoesNotExist avgt 30 1921.690 ± 78.898 ns/op
[info] StdLibEnumBenchmarks.withNameExists avgt 30 56.517 ± 1.161 ns/op
[info] values.ValueEnumBenchmarks.withValueDoesNotExist avgt 30 1950.291 ± 29.292 ns/op
[info] values.ValueEnumBenchmarks.withValueExists avgt 30 4.009 ± 0.062 ns/op
[info] values.ValueEnumBenchmarks.withValueOptDoesNotExist avgt 30 5.285 ± 0.063 ns/op
[info] values.ValueEnumBenchmarks.withValueOptExists avgt 30 6.621 ± 0.084 ns/op
Benchmark results for Scala version 2.11 on Linux 4.8.13-1-ARCH with Intel® Core™ i7-6600U CPU @ 2.60GHzm and Turbo enabled:
[info] Benchmark Mode Cnt Score Error Units
[info] EnumBenchmarks.indexOf avgt 30 11.211 ± 0.805 ns/op
[info] EnumBenchmarks.withNameDoesNotExist avgt 30 1369.675 ± 97.946 ns/op
[info] EnumBenchmarks.withNameExists avgt 30 11.727 ± 0.473 ns/op
[info] EnumBenchmarks.withNameOptionDoesNotExist avgt 30 5.148 ± 0.345 ns/op
[info] EnumBenchmarks.withNameOptionExists avgt 30 8.035 ± 0.228 ns/op
[info] StdLibEnumBenchmarks.withNameDoesNotExist avgt 30 1538.766 ± 163.269 ns/op
[info] StdLibEnumBenchmarks.withNameExists avgt 30 54.505 ± 1.419 ns/op
[info] values.ValueEnumBenchmarks.withValueDoesNotExist avgt 30 1473.439 ± 78.215 ns/op
[info] values.ValueEnumBenchmarks.withValueExists avgt 30 3.175 ± 0.111 ns/op
[info] values.ValueEnumBenchmarks.withValueOptDoesNotExist avgt 30 4.749 ± 0.159 ns/op
[info] values.ValueEnumBenchmarks.withValueOptExists avgt 30 5.639 ± 0.266 ns/op
And with Turbo disabled:
[info] Benchmark Mode Cnt Score Error Units
[info] EnumBenchmarks.indexOf avgt 30 13.080 ± 0.098 ns/op
[info] EnumBenchmarks.withNameDoesNotExist avgt 30 1672.842 ± 81.636 ns/op
[info] EnumBenchmarks.withNameExists avgt 30 17.010 ± 2.512 ns/op
[info] EnumBenchmarks.withNameOptionDoesNotExist avgt 30 6.561 ± 0.149 ns/op
[info] EnumBenchmarks.withNameOptionExists avgt 30 10.070 ± 0.065 ns/op
[info] StdLibEnumBenchmarks.withNameDoesNotExist avgt 30 1841.427 ± 42.465 ns/op
[info] StdLibEnumBenchmarks.withNameExists avgt 30 67.414 ± 2.356 ns/op
[info] values.ValueEnumBenchmarks.withValueDoesNotExist avgt 30 1713.638 ± 91.957 ns/op
[info] values.ValueEnumBenchmarks.withValueExists avgt 30 4.136 ± 0.183 ns/op
[info] values.ValueEnumBenchmarks.withValueOptDoesNotExist avgt 30 6.144 ± 0.215 ns/op
[info] values.ValueEnumBenchmarks.withValueOptExists avgt 30 6.766 ± 0.174 ns/op