Giter Club home page Giter Club logo

enumeratum's Introduction

Enumeratum Continuous integration codecov Codacy Badge Maven Central Scala.js Join the chat at https://gitter.im/lloydmeta/enumeratum

Enumeratum is a type-safe and powerful enumeration implementation for Scala that offers exhaustive pattern match warnings, integrations with popular Scala libraries, and idiomatic usage that won't break your IDE. It aims to be similar enough to Scala's built in Enumeration to be easy-to-use and understand while offering more flexibility, type-safety (see this blog post describing erasure on Scala's Enumeration), and richer enum values without having to maintain your own collection of values.

Enumeratum has the following niceties:

  • Zero dependencies
  • Performant: Faster thanEnumeration in the standard library (see benchmarks)
  • Allows your Enum members to be full-fledged normal objects with methods, values, inheritance, etc.
  • ValueEnums that map to various primitive values and have compile-time uniqueness constraints.
  • 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)
  • No usage of synchronized, which may help with performance and deadlocks prevention
  • All magic happens at compile-time so you know right away when things go awry
  • Comprehensive automated testing to make sure everything is in tip-top shape

Enumeratum is published for Scala 2.12.x, 2.13.x, 3.x as well as ScalaJS and ScalaNative.

Note that there are a couple of limitations for 3.x:

  • All "immediate" parent types of enum entries need to be sealed (reasoning here)
  • The -Yretain-trees Scalac option must be set when using ValueEnums

Integrations are available for:

Table of Contents

  1. Quick start
  2. More examples
  3. ScalaJS
  4. Play integration
  5. Play JSON integration
  6. Circe integration
  7. ReactiveMongo BSON integration
  8. Argonaut integration
  9. Json4s integration
  10. Slick integration
  11. ScalaCheck
  12. Quill integration
  13. Cats integration
  14. Doobie integration
  15. Anorm integration
  16. Benchmarking
  17. Publishing

Quick start

SBT

Maven Central

In build.sbt, set the Enumeratum version in a variable (for the latest version, set val enumeratumVersion = the version you see in the Maven badge above).

libraryDependencies ++= Seq(
    "com.beachape" %% "enumeratum" % enumeratumVersion
)

Enumeratum has different integrations that can be added to your build ร  la carte. For more info, see the respective sections in the Table of Contents

Usage

Using Enumeratum is simple. Just declare your own sealed trait or class A that extends EnumEntry and implement it as case objects inside an object that extends from Enum[A] as shown below.

import enumeratum._

sealed trait Greeting extends EnumEntry

object Greeting extends Enum[Greeting] {

  /*
   `findValues` is a protected method that invokes a macro to find all `Greeting` object declarations inside an `Enum`

   You use it to implement the `val values` member
  */
  val values = findValues

  case object Hello   extends Greeting
  case object GoodBye extends Greeting
  case object Hi      extends Greeting
  case object Bye     extends Greeting

}

// Object Greeting has a `withName(name: String)` method
Greeting.withName("Hello")
// => res0: Greeting = Hello

Greeting.withName("Haro")
// => java.lang.NoSuchElementException: Haro is not a member of Enum (Hello, GoodBye, Hi, Bye)

// A safer alternative would be to use `withNameOption(name: String)` method which returns an Option[Greeting]
Greeting.withNameOption("Hello")
// => res1: Option[Greeting] = Some(Hello)

Greeting.withNameOption("Haro")
// => res2: Option[Greeting] = None

// It is also possible to use strings case insensitively
Greeting.withNameInsensitive("HeLLo")
// => res3: Greeting = Hello

Greeting.withNameInsensitiveOption("HeLLo")
// => res4: Option[Greeting] = Some(Hello)

// Uppercase-only strings may also be used
Greeting.withNameUppercaseOnly("HELLO")
// => res5: Greeting = Hello

Greeting.withNameUppercaseOnlyOption("HeLLo")
// => res6: Option[Greeting] = None

// Similarly, lowercase-only strings may also be used
Greeting.withNameLowercaseOnly("hello")
// => res7: Greeting = Hello

Greeting.withNameLowercaseOnlyOption("hello")
// => res8: Option[Greeting] = Some(Hello)

Note that by default, findValues will return a Seq with the enum members listed in written-order (relevant if you want to use the indexOf method).

Enum members found in nested objects will be included by findValues as well, and will appear in the order they are written in the companion object, top to bottom. Note that enum members declared in traits or classes will not be discovered by findValues. For example:

sealed trait Nesting extends EnumEntry
object Nesting extends Enum[Nesting] {
  val values = findValues

  case object Hello extends Nesting
  object others {
    case object GoodBye extends Nesting
  }
  case object Hi extends Nesting
  class InnerClass {
    case object NotFound extends Nesting
  }
}

Nesting.values
// => res0: scala.collection.immutable.IndexedSeq[Nesting] = Vector(Hello, GoodBye, Hi)

For an interactive demo, checkout this Scastie snippet.

More examples

Enum

Continuing from the Greeting enum declared in the quick-start section:

import Greeting._

def tryMatching(v: Greeting): Unit = v match {
  case Hello   => println("Hello")
  case GoodBye => println("GoodBye")
  case Hi      => println("Hi")
}

/**
Pattern match warning ...

<console>:24: warning: match may not be exhaustive.
It would fail on the following input: Bye
       def tryMatching(v: Greeting): Unit = v match {

*/

Greeting.indexOf(Bye)
// => res2: Int = 3

The name is taken from the toString method of the particular EnumEntry. This behavior can be changed in two ways.

Manual override of name

The first way to change the name behaviour is to manually override the def entryName: String method.

import enumeratum._

sealed abstract class State(override val entryName: String) extends EnumEntry

object State extends Enum[State] {

   val values = findValues

   case object Alabama extends State("AL")
   case object Alaska  extends State("AK")
   // and so on and so forth.
}

import State._

State.withName("AL")

Mixins to override the name

The second way to override the name behaviour is to mixin the stackable traits provided for common string conversions, Snakecase, UpperSnakecase, CapitalSnakecase, Hyphencase, UpperHyphencase, CapitalHyphencase, Dotcase, UpperDotcase, CapitalDotcase, Words, UpperWords, CapitalWords, Camelcase, LowerCamelcase, Uppercase, Lowercase, and Uncapitalised.

import enumeratum._
import enumeratum.EnumEntry._

sealed trait Greeting extends EnumEntry with Snakecase

object Greeting extends Enum[Greeting] {

  val values = findValues

  case object Hello        extends Greeting
  case object GoodBye      extends Greeting
  case object ShoutGoodBye extends Greeting with Uppercase

}

Greeting.withName("hello")
Greeting.withName("good_bye")
Greeting.withName("SHOUT_GOOD_BYE")

ValueEnum

Asides from enumerations that resolve members from String names, Enumeratum also supports ValueEnums, enums that resolve members from simple values like Int, Long, Short, Char, Byte, and String (without support for runtime transformations).

These enums are not modelled after Enumeration from standard lib, and therefore have the added ability to make sure, at compile-time, that multiple members do not share the same value.

import enumeratum.values._

sealed abstract class LibraryItem(val value: Int, val name: String) extends IntEnumEntry

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:

If you want to allow aliases in your enumeration, i.e. multiple entries that share the same value, you can extend the enumeratum.values.AllowAlias trait:

import enumeratum.values._

sealed abstract class Judgement(val value: Int) extends IntEnumEntry with AllowAlias

object Judgement extends IntEnum[Judgement] {

  case object Good extends Judgement(1)
  case object OK extends Judgement(2)
  case object Meh extends Judgement(2)
  case object Bad extends Judgement(3)

  val values = findValues

}

Calling withValue with an aliased value will return one of the corresponding entries. Which one it returns is undefined:

assert(Judgement.withValue(2) == Judgement.OK || Judgement.withValue(2) == Judgement.Meh)

Restrictions

  • ValueEnums must have their value members implemented as literal values.

ScalaJS

In a ScalaJS project, add the following to build.sbt:

Maven Central

libraryDependencies ++= Seq(
    "com.beachape" %%% "enumeratum" % enumeratumVersion
)

As expected, usage is exactly the same as normal Scala.

Play Integration

Maven Central

The enumeratum-play project is published separately and gives you access to various tools to help you avoid boilerplate in your Play project.

SBT

For enumeratum with full Play support:

libraryDependencies ++= Seq(
    "com.beachape" %% "enumeratum-play" % enumeratumPlayVersion
)

Dependencies and compatibility notes

  • As of version 1.8, enumeratum-play requires Scala 2.13+ and Play >= 3

Usage

PlayEnum

The included PlayEnum trait is probably going to be the most interesting as it includes a bunch of built-in implicits like Json formats, Path bindables, Query string bindables, and Form field support.

For example:

package enums

import enumeratum._

sealed trait Greeting extends EnumEntry

object Greeting extends PlayEnum[Greeting] {

  val values = findValues

  case object Hello   extends Greeting
  case object GoodBye extends Greeting
  case object Hi      extends Greeting
  case object Bye     extends Greeting

}

/*
  Then make sure to import your PlayEnums into your routes in your Build.scala
  or build.sbt so that you can use them in your routes file.

  `routesImport += "enums._"`
*/


// You can also use the String Interpolating Routing DSL:

import play.api.routing.sird._
import play.api.routing._
import play.api.mvc._
Router.from {
    case GET(p"/hello/${Greeting.fromPath(greeting)}") => Action {
      Results.Ok(s"$greeting")
    }
}

PlayValueEnums

There are IntPlayEnum, LongPlayEnum, and ShortPlayEnum traits for use with IntEnumEntry, LongEnumEntry, and ShortEnumEntry respectively that provide Play-specific implicits as with normal PlayEnum. For example:

import enumeratum.values._

sealed abstract class PlayLibraryItem(val value: Int, val name: String) extends IntEnumEntry

case object PlayLibraryItem extends IntPlayEnum[PlayLibraryItem] {

  // A good mix of named, unnamed, named + unordered args
  case object Book     extends PlayLibraryItem(value = 1, name = "book")
  case object Movie    extends PlayLibraryItem(name = "movie", value = 2)
  case object Magazine extends PlayLibraryItem(3, "magazine")
  case object CD       extends PlayLibraryItem(4, name = "cd")

  val values = findValues

}

import play.api.libs.json.{ JsNumber, JsString, Json => PlayJson }
PlayLibraryItem.values.foreach { item =>
    assert(PlayJson.toJson(item) == JsNumber(item.value))
}

PlayFormFieldEnum

PlayEnum extends the trait PlayFormFieldEnum wich offers formField for mapping within a play.api.data.Form object.

import play.api.data.Form
import play.api.data.Forms._

object GreetingForm {
  val form = Form(
    mapping(
      "name"     -> nonEmptyText,
      "greeting" -> Greeting.formField
    )(Data.apply)(Data.unapply)
  )

  case class Data(
    name: String,
    greeting: Greeting)
}

Another alternative (if for example your Enum can't extend PlayEnum or PlayFormFieldEnum) is to create an implicit Format and bring it into scope using Play's of, i.e.

import play.api.data.Form
import play.api.data.Forms._

object Formats {
  implicit val greetingFormat = enumeratum.Forms.format(Greeting)
}

object GreetingForm {
  import Formats._

  val form = Form(
      mapping(
        "name"     -> nonEmptyText,
        "greeting" -> of[Greeting]
      )(Data.apply)(Data.unapply)
    )

    case class Data(
      name: String,
      greeting: Greeting)

}

Play JSON

Maven Central

The enumeratum-play-json project is published separately and gives you access to Play's auto-generated boilerplate for JSON serialization in your Enum's.

SBT

libraryDependencies ++= Seq(
    "com.beachape" %% "enumeratum-play-json" % enumeratumPlayJsonVersion
)

Usage

PlayJsonEnum

There are also PlayInsensitiveJsonEnum, PlayLowercaseJsonEnum, and PlayUppercaseJsonEnum traits for use. For example:

import enumeratum.{ PlayJsonEnum, Enum, EnumEntry }

sealed trait Greeting extends EnumEntry

object Greeting extends Enum[Greeting] with PlayJsonEnum[Greeting] {

  val values = findValues

  case object Hello   extends Greeting
  case object GoodBye extends Greeting
  case object Hi      extends Greeting
  case object Bye     extends Greeting

}

PlayJsonValueEnum

There are IntPlayJsonValueEnum, LongPlayJsonValueEnum, and ShortPlayJsonValueEnum traits for use with IntEnumEntry, LongEnumEntry, and ShortEnumEntry respectively. For example:

import enumeratum.values._

sealed abstract class JsonDrinks(val value: Short, name: String) extends ShortEnumEntry

case object JsonDrinks extends ShortEnum[JsonDrinks] with ShortPlayJsonValueEnum[JsonDrinks] {

  case object OrangeJuice extends JsonDrinks(value = 1, name = "oj")
  case object AppleJuice  extends JsonDrinks(value = 2, name = "aj")
  case object Cola        extends JsonDrinks(value = 3, name = "cola")
  case object Beer        extends JsonDrinks(value = 4, name = "beer")

  val values = findValues

}

import play.api.libs.json.{ JsNumber, JsString, Json => PlayJson, JsSuccess }

// Use to deserialise numbers to enum members directly
JsonDrinks.values.foreach { drink =>
    assert(PlayJson.toJson(drink) == JsNumber(drink.value))
}
assert(PlayJson.fromJson[JsonDrinks](JsNumber(3)) == JsSuccess(JsonDrinks.Cola))
assert(PlayJson.fromJson[JsonDrinks](JsNumber(19)).isError)

Circe

Maven Central

SBT

To use enumeratum with Circe:

libraryDependencies ++= Seq(
    "com.beachape" %% "enumeratum-circe" % enumeratumCirceVersion
)

To use with ScalaJS:

libraryDependencies ++= Seq(
    "com.beachape" %%% "enumeratum-circe" % enumeratumCirceVersion
)

Usage

Enum

import enumeratum._

sealed trait ShirtSize extends EnumEntry

case object ShirtSize extends Enum[ShirtSize] with CirceEnum[ShirtSize] {

  case object Small  extends ShirtSize
  case object Medium extends ShirtSize
  case object Large  extends ShirtSize

  val values = findValues

}

import io.circe.Json
import io.circe.syntax._

ShirtSize.values.foreach { size =>
    assert(size.asJson == Json.fromString(size.entryName))
}

ValueEnum

import enumeratum.values._

sealed abstract class CirceLibraryItem(val value: Int, val name: String) extends IntEnumEntry

case object CirceLibraryItem extends IntEnum[CirceLibraryItem] with IntCirceEnum[CirceLibraryItem] {

  // A good mix of named, unnamed, named + unordered args
  case object Book     extends CirceLibraryItem(value = 1, name = "book")
  case object Movie    extends CirceLibraryItem(name = "movie", value = 2)
  case object Magazine extends CirceLibraryItem(3, "magazine")
  case object CD       extends CirceLibraryItem(4, name = "cd")

  val values = findValues

}

import io.circe.Json
import io.circe.syntax._

CirceLibraryItem.values.foreach { item =>
    assert(item.asJson == Json.fromInt(item.value))
}

ReactiveMongo BSON

Maven Central

The enumeratum-reactivemongo-bson project is published separately and gives you access to ReactiveMongo's auto-generated boilerplate for BSON serialization in your Enum's.

SBT

libraryDependencies ++= Seq(
    "com.beachape" %% "enumeratum-reactivemongo-bson" % enumeratumReactiveMongoVersion
)

Usage

ReactiveMongoBsonEnum

For example:

import enumeratum.{ ReactiveMongoBsonEnum, Enum, EnumEntry }

sealed trait Greeting extends EnumEntry

object Greeting extends Enum[Greeting] with ReactiveMongoBsonEnum[Greeting] {

  val values = findValues

  case object Hello   extends Greeting
  case object GoodBye extends Greeting
  case object Hi      extends Greeting
  case object Bye     extends Greeting

}

ReactiveMongoBsonValueEnum

There are IntReactiveMongoBsonValueEnum, LongReactiveMongoBsonValueEnum, and ShortReactiveMongoBsonValueEnum traits for use with IntEnumEntry, LongEnumEntry, and ShortEnumEntry respectively. For example:

import enumeratum.values._

sealed abstract class BsonDrinks(val value: Short, name: String) extends ShortEnumEntry

case object BsonDrinks extends ShortEnum[BsonDrinks] with ShortReactiveMongoBsonValueEnum[BsonDrinks] {

  case object OrangeJuice extends BsonDrinks(value = 1, name = "oj")
  case object AppleJuice  extends BsonDrinks(value = 2, name = "aj")
  case object Cola        extends BsonDrinks(value = 3, name = "cola")
  case object Beer        extends BsonDrinks(value = 4, name = "beer")

  val values = findValues

}

import reactivemongo.api.bson._

// Use to deserialise numbers to enum members directly
BsonDrinks.values.foreach { drink =>
  val writer = implicitly[BSONWriter[BsonDrinks]]

  assert(writer.write(drink) == BSONInteger(drink.value))
}

val reader = implicitly[BSONReader[BsonDrinks]]

assert(reader.read(BSONInteger(3)) == BsonDrinks.Cola)

Argonaut

Maven Central

SBT

To use enumeratum with Argonaut:

libraryDependencies ++= Seq(
    "com.beachape" %% "enumeratum-argonaut" % enumeratumArgonautVersion
)

Usage

Enum

import enumeratum._

sealed trait TrafficLight extends EnumEntry
object TrafficLight extends Enum[TrafficLight] with ArgonautEnum[TrafficLight] {
  case object Red    extends TrafficLight
  case object Yellow extends TrafficLight
  case object Green  extends TrafficLight
  val values = findValues
}

import argonaut._
import Argonaut._

TrafficLight.values.foreach { entry =>
    assert(entry.asJson == entry.entryName.asJson)
}

ValueEnum

import enumeratum.values._

sealed abstract class ArgonautDevice(val value: Short) extends ShortEnumEntry
case object ArgonautDevice
    extends ShortEnum[ArgonautDevice]
    with ShortArgonautEnum[ArgonautDevice] {
  case object Phone   extends ArgonautDevice(1)
  case object Laptop  extends ArgonautDevice(2)
  case object Desktop extends ArgonautDevice(3)
  case object Tablet  extends ArgonautDevice(4)

  val values = findValues
}

import argonaut._
import Argonaut._

ArgonautDevice.values.foreach { item =>
    assert(item.asJson == item.value.asJson)
}

Json4s

Maven Central

SBT

To use enumeratum with Json4s:

libraryDependencies ++= Seq(
    "com.beachape" %% "enumeratum-json4s" % enumeratumJson4sVersion
)

Usage

Enum

import enumeratum._

sealed trait TrafficLight extends EnumEntry
object TrafficLight extends Enum[TrafficLight] /* nothing extra here */ {
  case object Red    extends TrafficLight
  case object Yellow extends TrafficLight
  case object Green  extends TrafficLight

  val values = findValues
}

import org.json4s.DefaultFormats

implicit val formats = DefaultFormats + Json4s.serializer(TrafficLight)

ValueEnum

import enumeratum.values._

sealed abstract class Device(val value: Short) extends ShortEnumEntry
case object Device
  extends ShortEnum[Device] /* nothing extra here */  {
  case object Phone   extends Device(1)
  case object Laptop  extends Device(2)
  case object Desktop extends Device(3)
  case object Tablet  extends Device(4)

  val values = findValues
}

import org.json4s.DefaultFormats

implicit val formats = DefaultFormats + Json4s.serializer(Device)

ScalaCheck

Maven Central

SBT

To use enumeratum with ScalaCheck:

libraryDependencies ++= Seq(
    "com.beachape" %% "enumeratum-scalacheck" % enumeratumScalacheckVersion
)

Usage

Enum

Given the enum declared in the quick-start section, you can get an Arbitrary[Greeting] (to generate instances of Greeting) and a Cogen[Greeting] (to generate instances of Greeting => (A: Arbitrary)) by importing generators in the scope of your tests:

import enumeratum.scalacheck._

ValueEnum

Similarly, you can get Arbitrary and Cogen instances for every ValueEnum subtype by importing generators in the scope of your tests:

import enumeratum.values.scalacheck._

Quill

Maven Central

SBT

To use enumeratum with Quill:

libraryDependencies ++= Seq(
    "com.beachape" %% "enumeratum-quill" % enumeratumQuillVersion
)

To use with ScalaJS:

libraryDependencies ++= Seq(
    "com.beachape" %%% "enumeratum-quill" % enumeratumQuillVersion
)

Usage

Enum

import enumeratum._

sealed trait ShirtSize extends EnumEntry

case object ShirtSize extends Enum[ShirtSize] with QuillEnum[ShirtSize] {

  case object Small  extends ShirtSize
  case object Medium extends ShirtSize
  case object Large  extends ShirtSize

  val values = findValues

}

case class Shirt(size: ShirtSize)

import io.getquill._

lazy val ctx = new PostgresJdbcContext(SnakeCase, "ctx")
import ctx._

ctx.run(query[Shirt].insert(_.size -> lift(ShirtSize.Small: ShirtSize)))

ctx.run(query[Shirt]).foreach(println)
  • Note that a type ascription to the EnumEntry trait (eg. ShirtSize.Small: ShirtSize) is required when binding hardcoded EnumEntrys

ValueEnum

import enumeratum._

sealed abstract class ShirtSize(val value: Int) extends IntEnumEntry

case object ShirtSize extends IntEnum[ShirtSize] with IntQuillEnum[ShirtSize] {

  case object Small  extends ShirtSize(1)
  case object Medium extends ShirtSize(2)
  case object Large  extends ShirtSize(3)

  val values = findValues

}

case class Shirt(size: ShirtSize)

import io.getquill._

lazy val ctx = new PostgresJdbcContext(SnakeCase, "ctx")
import ctx._

ctx.run(query[Shirt].insert(_.size -> lift(ShirtSize.Small: ShirtSize)))

ctx.run(query[Shirt]).foreach(println)
  • Note that a type ascription to the ValueEnumEntry abstract class (eg. ShirtSize.Small: ShirtSize) is required when binding hardcoded ValueEnumEntrys
  • quill-cassandra currently does not support ShortEnum and ByteEnum (see getquill/quill#1009)
  • quill-orientdb currently does not support ByteEnum (see getquill/quill#1029)

Slick integration

Maven Central

Column Mappings

In order to use your enumeratum Enums in Slick tables as columns, you will need to construct instances of MappedColumnType and make them available where you define and query your slick tables. In order to more easily construct these instances, the enumeratum-slick integration provides a trait enumeratum.SlickEnumSupport. This trait provides a method mappedColumnTypeForEnum (and variants) for constructing a mapped column type for your enum. For example if you want to use Enum[Greeting] in your slick table, mix in SlickEnumSupport where you define your table.

trait GreetingRepository extends SlickEnumSupport {
  val profile: slick.jdbc.Profile
  implicit lazy val greetingMapper = mappedColumnTypeForEnum(Greeting)
  class GreetingTable(tag: Tag) extends Table[(String, Greeting)](tag, "greeting") {
    def id = column[String]("id", O.PrimaryKey)
    def greeting = column[Greeting]("greeting") // Maps to a varchar/text column

    def * = (id, greeting)
  }

ValueEnum Mappings

If you want to represent a ValueEnum by its value rather than its string name, simply mix in SlickValueEnumSupport and proceed mostly as above:

implicit lazy val libraryItemMapper = mappedColumnTypeForIntEnum(LibraryItem)
...
def item = column[LibraryItem]("LIBRARY_ITEM") // Maps to a numeric column

Common Mappers

An alternate approach which is useful when mappers need to be shared across repositories (perhaps for something common like a "Status" enum) is to define your mappers in a module on their own, then make use of them in your repositories:

trait CommonMappers extends SlickEnumSupport {
  val profile: Profile
  implicit lazy val statusMapper = mappedColumnTypeForEnum(Status)
  ...
}
trait UserRepository extends CommonMappers {
  val profile: Profile
  class UserTable(tag: Tag) extends Table[UserRow](tag, "user") {
    ...
    def status = column[Status]("status")
    ...
  }
}

Querying by enum column types

Note that because your enum values are singleton objects, you may get errors when you try to use them in Slick queries like the following:

.filter(_.trafficLight === TrafficLight.Red)

This is because TrafficLight.Red in the above example is inferred to be of its unique type (TrafficLight.Red) rather than TrafficLight, thus causing a failure to find your mapping. In order to fix this, simply assist the compiler by ascribing the type to be TrafficLight:

.filter(_.trafficLight === (TrafficLight.Red: TrafficLight))

A way around this if you find the type expansion offensive is to define val accessors for your enum entries that are typed as the parent type. You can do this inside your Enums companion object or more locally:

val red: TrafficLight = Red // Not red: TrafficLight.Red = Red
val yellow: TrafficLight = Yellow
val green: TrafficLight = Green
...
.filter(_.trafficLight === red)

Interpolated / Plain SQL integration

If you want to use slick interpolated SQL queries you can use the provided constructors to instantiate instances of GetResult[_] and SetParameter[_] for your enum:

import SlickEnumPlainSqlSupport._

Or mix it in...

trait Foo extends SlickEnumPlainSqlSupport {
  ...
}

Then define your instances:

implicit val greetingGetResult = getResultForEnum(Greeting)
implicit val greetingOptionGetResult = optionalGetResultForEnum(Greeting)
implicit val greetingSetParameter = setParameterForEnum(Greeting)
implicit val greetingOptionSetParameter = optionalSetParameterForEnum(Greeting)

Cats

Maven Central

SBT

To use enumeratum with Cats:

libraryDependencies ++= Seq(
    "com.beachape" %% "enumeratum-cats" % enumeratumCatsVersion
)

To use with ScalaJS:

libraryDependencies ++= Seq(
    "com.beachape" %%% "enumeratum-cats" % enumeratumCatsVersion
)

Usage

This enumeratum module is mostly useful for generic derivation - providing instances for Eq, Show and Hash. So if you have structures (for example case classes) which contain enum values, you get the instances for the enum itself "for free". But it can be useful for standalone usage as, providing type-safe comparison and hashing.

Enum

import enumeratum._

sealed trait ShirtSize extends EnumEntry

case object ShirtSize extends Enum[ShirtSize] with CatsEnum[ShirtSize] {

  case object Small   extends ShirtSize
  case object Medium  extends ShirtSize
  case object Large   extends ShirtSize

  val values = findValues

}

import cats.syntax.eq._
import cats.syntax.show._

val shirtSizeOne: ShirtSize = ...
val shirtSizeTwo: ShirtSize = ...

if(shirtSizeOne === shirtSizeTwo) { // note the third equals
    printf("We got the same size, its hash is: %i", implicitly[Hash[TrafficLight]].hash(shirtSizeOne))
} else {
    printf("Shirt sizes mismatch: %s =!= %s", shirtSizeOne.show, shirtSizeTwo.show)
}

ValueEnum

There are two implementations for ValueEnum:

  • CatsValueEnum provides the same functionality as CatsEnum (except Hash)
  • CatsOrderValueEnum provides the same functionality as CatsValueEnum plus an instance of cats.Order (due to Scala 2 trait limitations, it's an abstract class, check out CatsCustomOrderValueEnum if you need a trait)
import enumeratum.values._

sealed abstract class CatsPriority(val value: Int, val name: String) extends IntEnumEntry

case object CatsPriority extends IntEnum[CatsPriority] with CatsOrderValueEnum[Int, CatsPriority] {

  // A good mix of named, unnamed, named + unordered args
  case object Low         extends CatsPriority(value = 1, name = "low")
  case object Medium      extends CatsPriority(name = "medium", value = 2)
  case object High        extends CatsPriority(3, "high")
  case object SuperHigh   extends CatsPriority(4, name = "super_high")

  val values = findValues

}

import cats.instances.int._
import cats.instances.list._
import cats.syntax.order._
import cats.syntax.foldable._

val items: List[CatsPriority] = List(High, Low, SuperHigh)

items.maximumOption // Some(SuperHigh)

Inheritance-free usage

If you need instances, but hesitate to mix in the traits demonstrated above, you can get them using the provided methods in enumeratum.Cats and enumeratum.values.Cats - the second also provides more flexibility than the (opinionated) mix-in trait as it allows to pass a custom type class instance for the value type (methods names are prefixed with value).

Doobie

Maven Central

SBT

To use enumeratum with Doobie:

libraryDependencies ++= Seq(
    "com.beachape" %% "enumeratum-doobie" % enumeratumDoobieVersion
)

To use with ScalaJS:

libraryDependencies ++= Seq(
    "com.beachape" %%% "enumeratum-doobie" % enumeratumDoobieVersion
)

Usage

Enum

If you need to store enum values in text column of following table

CREATE TABLE clothes (
  shirt varchar(100)
)

you should use following code

import enumeratum._

sealed trait ShirtSize extends EnumEntry

case object ShirtSize extends Enum[ShirtSize] with DoobieEnum[ShirtSize] {

  case object Small  extends ShirtSize
  case object Medium extends ShirtSize
  case object Large  extends ShirtSize

  val values = findValues

}

case class Shirt(size: ShirtSize)

import doobie._
import doobie.implicits._
import doobie.util.ExecutionContexts
import cats.effect._

implicit val cs = IO.contextShift(ExecutionContexts.synchronous)

val xa = Transactor.fromDriverManager[IO](
  "org.postgresql.Driver",
  "jdbc:postgresql:world",
  "postgres",
  "",
  Blocker.liftExecutionContext(ExecutionContexts.synchronous)
)

sql"insert into clothes (shirt) values (${Shirt(ShirtSize.Small)})".update.run
  .transact(xa)
  .unsafeRunSync

sql"select shirt from clothes"
  .query[Shirt]
  .to[List]
  .transact(xa)
  .unsafeRunSync
  .take(5)
  .foreach(println)
  • Note that a type ascription to the EnumEntry trait (eg. ShirtSize.Small: ShirtSize) is required when binding hardcoded EnumEntrys

ValueEnum

import enumeratum.values.{ IntDoobieEnum, IntEnum, IntEnumEntry }

sealed abstract class ShirtSize(val value: Int) extends IntEnumEntry

case object ShirtSize extends IntEnum[ShirtSize] with IntDoobieEnum[ShirtSize] {

  case object Small  extends ShirtSize(1)
  case object Medium extends ShirtSize(2)
  case object Large  extends ShirtSize(3)

  val values = findValues

}

case class Shirt(size: ShirtSize)

import doobie._
import doobie.implicits._
import doobie.util.ExecutionContexts
import cats.effect._

implicit val cs = IO.contextShift(ExecutionContexts.synchronous)

val xa = Transactor.fromDriverManager[IO](
  "org.postgresql.Driver",
  "jdbc:postgresql:world",
  "postgres",
  "",
  Blocker.liftExecutionContext(ExecutionContexts.synchronous)
)

sql"insert into clothes (shirt) values (${Shirt(ShirtSize.Small)})".update.run
  .transact(xa)
  .unsafeRunSync

sql"select shirt from clothes"
  .query[Shirt]
  .to[List]
  .transact(xa)
  .unsafeRunSync
  .take(5)
  .foreach(println)
  • Note that a type ascription to the ValueEnumEntry abstract class (eg. ShirtSize.Small: ShirtSize) is required when binding hardcoded ValueEnumEntrys

Anorm

Anorm provides a module to support Enum as Column and parameters.

Benchmarking

Benchmarking is in the unpublished benchmarking project. It uses JMH and you can run them in the sbt console by issuing the following command from your command line:

sbt +benchmarking/'jmh:run -i 10 -wi 10 -f3 -t 1'

The above command will run JMH benchmarks against different versions of Scala. Leave off + to run against the main/latest supported version of Scala.

On my late 2013 MBP using Java8 on OSX El Capitan:

[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

Discussion

Other than the methods that throw NoSuchElementExceptions, performance is in the 10ns range (taking into account JMH overhead of roughly 2-3ns), which is acceptable for almost all use-cases. PRs that promise to increase performance are expected to be demonstrably faster.

Also, Enumeratum's withName is faster than the standard library's Enumeration, by around 4x in the case where an entry exists with the given name. My guess is this is because Enumeratum doesn't use any synchronized calls or volatile annotations. It is also faster in the case where there is no corresponding name, but not by a significant amount, perhaps because the high cost of throwing an exception masks any benefits.

Publishing

Projects are published independently of each other.

JVM + ScalaJS projects should have an aggregate project to make it easy to publish them, e.g. for enumeratum-circe:

$ sbt "project circe-aggregate" +clean +publish-signed

Should publish all needed artefacts. Note that sbt circe-aggregate/publish-signed will not work (ScalaJS gets skipped).

Contributions

Issues and PRs are more than welcome.

  • For bug fixes, enhancements, version bumps etc: please feel free to send a PR or issue
  • For new integrations: these are generally bigger investments, and not all projects are a good fit to be maintained by me, so it would be a good idea to send an issue first to gauge interest and fit. If you feel it's a faster/better to have a concrete PR to discuss things with, by all means, feel free to go that route too.

enumeratum's People

Contributors

a1kemist avatar aaabramov avatar augi avatar avdv avatar bomgar avatar cb372 avatar cchantep avatar d3r1v3d avatar daniel-shuy avatar falconepl avatar gaeljw avatar guilgaly avatar jtjeferreira avatar lambdista avatar limansky avatar lloydmeta avatar mdedetrich avatar michaelt293 avatar mikaelstaldal avatar morgen-peschke avatar neilchaudhuri avatar nrinaudo avatar olegych avatar olsio avatar pdalpra avatar pedrorijo91 avatar pjfanning avatar rpiaggio avatar yaroot avatar zeapo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

enumeratum's Issues

Feature request: Enum.materializeEnum for generic T <: EnumEntry

Given:

import enumeratum.{Enum, EnumEntry}

class EntryGetter[T <: EnumEntry] {
  def getEntry(name: String): T = Enum.materializeEnum[T].withName(name)
}

I get the error Could not find the companion object for type type T.

I'm new to macros / reflection in Scala so for all I know it might not be possible to pull off, but it'd be awesome if this sort of generic lookup could work. My use-case is that I was trying to build a generic EnumEntry parser for use with case-app.

Scalafmt setup throws `NoSuchElementException`

When cloning the repo and running sbt with +benchmarking/jmh:run -i 10 -wi 10 -f3 -t 1, I get the following:

> last benchmarking/compile:scalafmt
java.lang.NoSuchMethodError: scala.Option.contains(Ljava/lang/Object;)Z
	at org.scalafmt.cli.StyleCache$.getStyleForFileOrError(StyleCache.scala:25)
	at org.scalafmt.cli.Scalafmt210.format(Scalafmt210.scala:21)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.scalafmt.sbt.HasScalaFmt.org$scalafmt$sbt$HasScalaFmt$$handleFile(HasScalaFmt.scala:85)
	at org.scalafmt.sbt.HasScalaFmt$$anonfun$writeFormattedContentsToFiles$1$$anonfun$apply$3.apply(HasScalaFmt.scala:54)
	at org.scalafmt.sbt.HasScalaFmt$$anonfun$writeFormattedContentsToFiles$1$$anonfun$apply$3.apply(HasScalaFmt.scala:54)
	at scala.collection.Iterator$class.foreach(Iterator.scala:727)
	at scala.collection.parallel.immutable.ParHashSet$ParHashSetIterator.foreach(ParHashSet.scala:76)
	at scala.collection.parallel.ParIterableLike$Foreach.leaf(ParIterableLike.scala:975)
	at scala.collection.parallel.Task$$anonfun$tryLeaf$1.apply$mcV$sp(Tasks.scala:54)
	at scala.collection.parallel.Task$$anonfun$tryLeaf$1.apply(Tasks.scala:53)
	at scala.collection.parallel.Task$$anonfun$tryLeaf$1.apply(Tasks.scala:53)
	at scala.collection.parallel.Task$class.tryLeaf(Tasks.scala:56)
	at scala.collection.parallel.ParIterableLike$Foreach.tryLeaf(ParIterableLike.scala:972)
	at scala.collection.parallel.AdaptiveWorkStealingTasks$WrappedTask$class.internal(Tasks.scala:172)
	at scala.collection.parallel.AdaptiveWorkStealingForkJoinTasks$WrappedTask.internal(Tasks.scala:514)
	at scala.collection.parallel.AdaptiveWorkStealingTasks$WrappedTask$class.compute(Tasks.scala:162)
	at scala.collection.parallel.AdaptiveWorkStealingForkJoinTasks$WrappedTask.compute(Tasks.scala:514)
	at scala.concurrent.forkjoin.RecursiveAction.exec(RecursiveAction.java:160)
	at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
	at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
	at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
	at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)
[error] (benchmarking/compile:scalafmt) java.lang.NoSuchMethodError: scala.Option.contains(Ljava/lang/Object;)Z

I believe there is a problem with the setup of scalafmt. Can someone else confirm?

Cannot compile types with external ref through findValues

Version: latest

sealed abstract class A private (val value: Int) extends IntEnumEntry {
  val text: String
}

object A extends IntEnum[A] {

  val values = findValues

  case object A1 extends A(1) {
    val text = identity("something") // Error:(9, 16) object A1 has a value with the wrong type: something:class java.lang.String, instead of int.
    //val text = "something"
  }

  def identity(str: String) = str

}

No workaround known, blocker.

AbstractMethodError when decoding with Circe

I am having trouble using enumeratum with circe

import io.circe._
import io.circe.generic.auto._
import enumeratum.values._

sealed abstract class Action(val value: String, val name: String) extends StringEnumEntry
case object Action extends StringEnum[Action] with StringCirceEnum[Action] {

    case object Buy extends Action("B", "Buy")
    case object Sell extends Action("S", "Sell")

    val values = findValues
}

case class Trade(action: Action)

val t = Trade(Action.Buy)
decode[Trade](t.asJson.toString).fold(throw _, identity)

gives:

java.lang.AbstractMethodError: enumeratum.values.Circe$$anon$2.tryDecode(Lio/circe/ACursor;)Lscala/util/Either;
	at com.compay.engine.Tester$anon$exportDecoder$macro$48$1$$anon$12.apply(Tester.scala:38)
	at io.circe.generic.decoding.DerivedDecoder$$anon$1.apply(DerivedDecoder.scala:13)
	at io.circe.Decoder$class.decodeJson(Decoder.scala:42)
	at io.circe.generic.decoding.DerivedDecoder.decodeJson(DerivedDecoder.scala:6)
	at io.circe.Parser$class.decode(Parser.scala:10)
	at io.circe.jawn.JawnParser.decode(JawnParser.scala:8)
	....

This works when action is a String and enumeratum is not used.

Integration with Json4s

Would it be possible to have an integration with Json4s? Serializing an enum is trivial:

import enumeratum.EnumEntry
import org.json4s.CustomSerializer

object EnumSerializer extends CustomSerializer[EnumEntry](format => (
    PartialFunction.empty,
    {
      case x: EnumEntry => JString(x.entryName)
    }
  ))

But deserializing is not so easy, the tricky part is to instantiate the correct enum type.

Potential addition to Slick integration for easy MappedColumnType[ValueEnum] definitions

First off, I want to say thank you for Enumeratum, it really is The Missing Scala Enum Library (TM), and I wish I had discovered it sooner.

I just thought I would share a little helper trait and function I've been using for defining MappedColumnTypes for instances of ValueEnum[A] with less boilerplate:

import scala.reflect.ClassTag
import enumeratum.values.{ ValueEnum, ValueEnumEntry }
import slick.driver.JdbcProfile

trait Profile {
  val profile: JdbcProfile
}

trait MappedColumnTypeEnumSupport { this: Profile =>
  def mappedColumnTypeForValueEnum[V, E <: ValueEnumEntry[V]]
  (companion: ValueEnum[V, E])
  (implicit tag: ClassTag[E], v: profile.BaseColumnType[V]): profile.BaseColumnType[E] = {
    profile.MappedColumnType.base[E, V](
      { x => x.value },
      { v => companion.withValue(v) }
    )
  }
}

Usage (assuming a ValueEnum called Shape somewhere in scope):

trait MyMappers extends MappedColumnTypeEnumSupport {
  implicit lazy val shapeMapping: BaseColumnType[Shape] = mappedColumnTypeForValueEnum(Shape)
}

Seems like this is generic enough that it could potentially be included with the library to ease this use case?If so, I believe something similar could be done for the SetParameter definitions as well.

Thanks again for this great library!

Enum.withName safe alternative

Enum.withName throws an exception for invalid names. Would it be possible to provide a method that would simply return an option/try/something else instead? maybe even deprecate the current method

Warning "Following value requires an explicit type annotation" for values = findValues

I get a warning "Public value requires an explicit type annotation" for the field

val values = findValues

My problem: the moment i change it to

val values:IndexedSeq[Greeting] = findValues

I get a compiler error: "value values has incompatible type"

Do i do something wrong or how can i fix this ?
Using: scala2.12, IntellijJ2016.3.3
(I would really like to avoid disabling this type annotation in my IDE, since it is useful everywhere else)

Toy example:

import enumeratum._

sealed trait Greeting extends EnumEntry {
}

object Greeting extends Enum[Greeting] {
	val values = findValues

	case object Hello extends Greeting

	case object Bue extends Greeting

	case object Hi extends Greeting
}

Better error message for withName

Currently withName produces error messages like

Baz is not a member of Enum (Bar, Boo)

where Enum is used regardless of the actual enum class/trait name. Is it possible to replace it with the actual class name?

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.

Unable to load enumeratum-circe with sbt 1.0.3 or 1.0.5

I think this is an sbt issue, and I filed an issue there: #3741

However, in case there's something in the way enumeratum-circe is packaged, that causes the problem to get triggered, maybe a solution can be found in this project as well. I have not seen similar with any other libraries I use (also normal enumeratum loads fine).

Git repo for fast trying out the issue: sbt-bug-enumeratum-circe

sbt-wartremover warns that inferred type containing Nothing

sbt-wartremover is a linter for Scala and I came across something peculiar while using it in conjunction with enumeratum:

    object Result extends Enum[Result] {
        val values = findValues

        final case class A() extends Result
        final case class B() extends Result
    }

Wart Remover fails on val values = findValues, saying "Inferred type containing Nothing".

I'm not sure why this is and would appreciate it if you could shed some light about this...

2.12 on-going TODOs

2.12 branch was finally merged, but there are still a few things to look out for

  • Start adding back various integration-related subprojects once they support 2.12
    • Play and Play-JSON: Ticket
    • ReactiveMongo
    • Argonaut Ticket
    • Upickle
  • Consider adding back Scalafmt again ticket

Macro Annotations

I wanted to write an efficient EnumSet/EnumMap for Enumeratum like Java's.

Java's EnumSet is essentially a bitmap. EnumSet.contains() uses the index of the Enum entry to perform a bitwise AND operation on the bitmap.

Java's EnumMap is essentially an array that stores each Value in the array index corresponding to the index of the Enum entry.

In both cases, to replicate the same behavior, given just an instance of EnumEntry, the Set/Map needs to be able to obtain the index of the EnumEntry. Currently, EnumEntry does not know what its index is.

I've been trying all sorts of ways to pass the index to each instance of EnumEntry without breaking encapsulation. Ideally, if possible, I wanted to do so without changing the way EnumEntry/Enum is used. Since Enum has valuesToIndex, one of the easiest ways to achieve this is to somehow pass the object extending Enum to the sealed trait/abstract class extending EnumEntry.

To avoid changing the way EnumEntry is used, initially I did:

abstract class EnumEntry()(implicit enum: Enum[_]) {

but somehow it didn't work. Even if it did, if there were multiple Enums in the same file, would the implicit enum parameter look-up the wrong Enum object instance?
I then tried:

abstract class EnumEntry[E <: EnumEntry]()(implicit enum: Enum[E]) {

which requires the type of the sealed trait/abstract class to be specified as a Type Parameter, eg.

sealed trait MyEnum extends EnumEntry[MyEnum]

I then tried to enforce the Type Parameter passed in to be the sealed trait/abstract class instance, and tried to allow the compiler to automatically infer the Type Parameter:

abstract class EnumEntry[+Self <: EnumEntry]()(implicit enum: Enum[Self]) {

or

abstract class EnumEntry[E <: EnumEntry]()(implicit enum: Enum[E]) {
    self: E =>

which can then be used as:

sealed abstract class MyEnum extends EnumEntry    // must be abstract class to infer Self

which would achieve what I want, but unfortunately, I get weird errors when compiling the tests:
error: not found: value <none>

which I can't for the life of me figure out the meaning of.


This then lead me to the idea of using Macro Annotations. If a class is annotated, both the class (sealed abstract class) and companion object is passed to the macro. Theoretically, we can then no longer bother with implicit parameters and pass in the object directly instead. Eg.

@Enum
sealed trait Temperature

object Temperature {
    case object Hot extends Temperature
    case object Room extends Temperature
    case object Cold extends Temperature
}

should be expanded by the macro to:

sealed trait Temperature extends EnumEntry(Temperature)    // not sure if you can pass the reference to the companion object like that

object Temperature extends Enum[Temperature] {
    val values = ...

    case object Hot extends Temperature
    case object Room extends Temperature
    case object Cold extends Temperature
}

I'm not sure if you can pass the reference to the companion object like that. If not, then maybe the EnumEntry constructor could take in a index: Int instead of an instance of Enum, but I think it may be useful for other purposes for the EnumEntry to have a reference to the Enum companion object.

Note that, in addition to automatically extending EnumEntry/Enum, we can also use the macro to implement values automatically, removing the need to manually call:

val values = findValues

killing 2 birds with one stone.

The downsides are, it breaks backwards compatibility, and implementers of Enumeratum will need to add the Macro Paradise compiler plugin to their project.

Find Value by Annotation

Hey!

Nice project!
I would like to have a bit of syntactic sugar: it would be nice to select the value field by an annotation to avoid the overhead of overriding the value method. Here is an example:

sealed abstract class Code(@Value val value: Int, val door: String) extends IntEnumEntry

Is it realizable?

Update Maven Badge for enumeratum-play-json

Hey there,

It looks like 1.5.12 is currently available on maven central, but the badge on the project for play-json is still on the 1.5.12-2.6.0-M7 milestone release version. Might be nice to have it updated for other's to use without having to check if the actual release if available. :)

Thanks,
Andrew

Camelcase mixin

I've noticed there's an EnumEntry mixin for pretty much any case I could want, except camelCase, which is by far the most common case I deal with.

Suggestion: Switch to Immutable collections for findValues

Most applications shouldn't be changing their enumerations mid-execution, but the standard scala.collection.Seq implementation doesn't have the immutable guarantees that scala.colleciton.immutable.Seq and immutable.IndexedSeq provide.

Of course immutable.Seq does implement scala.collection.Seq allowing backward compatibility, but allowing users to take advantage of the more specific case when useful (a number of libraries are demanding immutable collection parameters).

Any way for an Enum to inherit from another Enum?

Hi,

I have some enum values that are common to several enums.
How would I reuse the basegroup in GroupA so that it had metricA, metricB, etc?

Not sure if this is possible but I might be missing something.

import enumeratum._
import enumeratum.EnumEntry

sealed trait BaseGroup extends EnumEntry
object BaseGroup extends Enum[BaseGroup] {
  val values = findValues
  case object metricA extends BaseGroup
  case object metricB extends BaseGroup
  case object metricC extends BaseGroup

}
// simple inheritance doesn't seem to work
sealed trait GroupA extends EnumEntry
object GroupA extends Enum[GroupA] {
  val values = findValues
  case object metricD extends GroupA
  case object metricE extends GroupA

}

// How do I get it to be
// Vector(metricA, metricB, metricC, metricD, metricE)
// or sum both Base and GroupA
GroupA.values

Is there a way to do it? Thanks a lot!

enumeratum-json4s ValueEnum example broken?

When trying to implement (copy & paste really...) the json4s integration example for ValueEnum, trying to define the serialization format

implicit val formats = DefaultFormats + Json4s.serializer(Device)

I get the following compiler error:

Type mismatch, epxected: Enum[NotInferedA], actual: Device.type

This is the case for both version 1.5.12 and 1.5.13, using scala version 2.12.4.

Why is EnumEntry.entryName a def instead of a val?

First off, this is a great library, I hope it will eventually replace the built-in Enumeration in the Scala core library.

This is not really an issue, but as I was diving into to the code, I couldn't help but notice that entryName in EnumEntry is declared as a def. Doesn't that mean that if entryName is not overridden (eg. trait MyEnum extends EnumEntry), it would be re-evaluated each time entryName is used (even worse when a case-manipulating Trait, eg. CapitalSnakecase, is mixed in)?

I'm just curious, is there a reason for this design choice?

Name clash with Play for 'Json'

Thanks for the library, really useful.

Something that I've noticed is that the suggested way of importing enumeratum introduces a name clash with Play:

scala> def test() = {
     |   import enumeratum._
     |   import play.api.libs.json._
     |   Json.toJson("test")
     | }
<console>:19: error: reference to Json is ambiguous;
it is imported twice in the same scope by
import play.api.libs.json._
and import enumeratum._
         Json.toJson("test")

This can be worked around, but given that enumeratum is specifically designed to work well with Play's json support, this seemed a bit of a shame to me. Could we rename the enumeratum.Json object?

Case Sensitive withName's

Often when reading message blobs from outside parties the case sensitivity will vary from message to message. It's often nice to not have to worry "did I .toLowerCase? or should it be .toUpperCase" the specific values you're reading out.

object x has a Value with the wrong type

I get the following compile error:

Error(73,45): object Example1 has a Value with the wrong type: Hello World:class java.lang.String, instead of int.
val values: IndexedSeq[BugDemonstration] = findValues
  • I noticed that i get errors like that if i have functions inside objects that extend an Enum (like Example1)
  • The more complex the code in the function is - the more likely it is to happen.
  • I tried to use Strings instead of Table/Actor (of libgdx library) -> but then the error magically disappears ....
  • Bizarre behaviour: if "case object Example1" is commented out -> the code compiles and launches successfully
  • can this be fixed, or am I doing something wrong ?

Code to reproduce error:

import com.badlogic.gdx.backends.lwjgl.{LwjglApplication, LwjglApplicationConfiguration}
import com.badlogic.gdx.graphics.GL20
import com.badlogic.gdx._
import com.badlogic.gdx.scenes.scene2d._
import com.badlogic.gdx.scenes.scene2d.ui._
import com.badlogic.gdx.utils.viewport.ScreenViewport
import enumeratum.values.{IntEnum, IntEnumEntry}

import scala.collection.immutable.IndexedSeq

object Start1 extends App with ApplicationListener {

	var skin: Skin = _
	var stage: Stage = _
	var lastSelected: String = _
	var container: Table = _
	var table: Actor = _

	override def create(): Unit = {

		stage = new Stage(new ScreenViewport())
		skin = new Skin()

		// Container: List + Table
		container = new Table()
		container.setFillParent(true)
		stage.addActor(container)

		val a = BugDemonstration.Example2.create(skin)
		container.add(a)
	}

	override def resize(width: Int, height: Int): Unit = stage.getViewport.update(width, height, true)

	override def render(): Unit = {
		Gdx.gl.glClearColor(0.2f, 0.2f, 0.2f, 1)
		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
		stage.act()
		stage.draw()
	}

	override def pause(): Unit = {}

	override def resume(): Unit = {}

	override def dispose(): Unit = {
		stage.dispose()
		skin.dispose()
	}

	val config = new LwjglApplicationConfiguration()
	config.title = "Test"
	config.width = 1000
	config.height = 700
	val app = new LwjglApplication(this, config)
}

sealed abstract class BugDemonstration(val value: Int, val name: String) extends IntEnumEntry {

	import BugDemonstration._

	def create(skin: Skin): Actor

	def previous: BugDemonstration = if (this == values.head) this else BugDemonstration.withValue(this.value - 1)

	def next: BugDemonstration = if (this == values.last) this else BugDemonstration.withValue(this.value + 1)
}

case object BugDemonstration extends IntEnum[BugDemonstration] {

	val values: IndexedSeq[BugDemonstration] = findValues

	def withName(name: String): BugDemonstration = {
		values
			.find(_.name == name)
			.getOrElse(throw new IllegalArgumentException("name " + name + " not found"))
	}

	case object Example1 extends BugDemonstration(0, "HelloWorld") {
		override def create(skin: Skin): Actor = {
			val label = new Label("Hello World", skin)
			val table = new Table(skin)
			table.add(label)
			table
		}
	}

	case object Example2 extends BugDemonstration(1, "ToDo") {
		override def create(skin: Skin): Actor = {
			new Table(skin)
		}
	}
}

build.sbt


name := "Test 123"
version := "0.1"
scalaVersion := "2.12.1"
javacOptions ++= Seq("-source", "1.8", "-target", "1.8")
resolvers += Resolver.mavenLocal

libraryDependencies += "com.beachape" %% "enumeratum" % "1.5.2"

resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"

val gdxVersion = "1.9.5"

// libgdx
libraryDependencies += "com.badlogicgames.gdx" % "gdx" % gdxVersion
libraryDependencies += "com.badlogicgames.gdx" % "gdx-tools" % gdxVersion

// libgdx - pc
libraryDependencies += "com.badlogicgames.gdx" % "gdx-backend-lwjgl" % gdxVersion
libraryDependencies += "com.badlogicgames.gdx" % "gdx-platform" % gdxVersion classifier "natives-desktop"

1.4.11 breaks ValueEnums with private constructors

As @zifeo pointed out, this used to compile under > 1.4.11 but fails now.

trait CustomEnumEntry extends IntEnumEntry {
  val value: Int
  val name: String
}
trait CustomEnum[T <: CustomEnumEntry] extends IntEnum[T] {
  def apply(name: String): T =
    values.find(_.name == name).get
}
trait CustomEnumComparable[T <: CustomEnumEntry] {
  this: T =>
  def >=(that: T): Boolean =
    this.value >= that.value
}
sealed abstract class TestEnum private (val value: Int, val name: String)
  extends CustomEnumEntry with CustomEnumComparable[TestEnum]
object TestEnum extends CustomEnum[TestEnum] {
  val values = findValues
  case object A extends TestEnum(10, "a")
  case object B extends TestEnum(20, "b")
}

Doc comments on ValueEnum case objects make scaladoc fail

If you create a ValueEnum class which has doc comments on the ValueEnumEntry case classes, it will build just fine but scaladoc will fail with, for example: "looks like not all of the members have a literal/constant 'value:int' declaration, namely: object Foo."

My best guess is that scalac and scaladoc use the same AST, but scalac discards doc comment nodes early and scaladoc does not. When ValueEnumMacros.findValuesForSubclassTrees starts parsing through the ValueEnum, the extra unexpected AST nodes block its attempt to figure out the values for the cases.

The following code will demonstrate the problem. If you remove the doc comment or move it from Foo to values, scaladoc will succeed. But with the doc comment on Foo, it will fail.

import enumeratum.values.{ShortEnum, ShortEnumEntry}
sealed abstract class Test(val value: Short) extends ShortEnumEntry
case object Test extends ShortEnum[Test] {
  /** Blah blah blah */
  case object Foo extends Test(0)
  val values = findValues
}

This was observed using Scala 2.11.8 and enumeratum 1.5.8.
(Also further checked against Scala 2.12.1, where the behavior seems the same.)

No valid constructor

First of all: thanks for this awesome library! I hope it finds its way into core Scala proper :)

I'm having some issues with enums that override entryName when I use them together with Spark. I think the problem is in deserializing enums. This is the relevant enum code:

sealed abstract class PhBoardingStatus(override val entryName: String) extends EnumEntry
object PhBoardingStatus extends Enum[PhBoardingStatus] {

  val values: IndexedSeq[PhBoardingStatus] = findValues

  case object All           extends PhBoardingStatus("ALL")
  case object Closed        extends PhBoardingStatus("BC")
  case object Ignored       extends PhBoardingStatus("BI")
  case object NotOpen       extends PhBoardingStatus("BN")
  case object Open          extends PhBoardingStatus("BO")
  case object Suspended     extends PhBoardingStatus("BS")
}

When using this enum in Spark code, this stacktrace gets thrown:

2017-05-16 09:22:37 WARN  org.apache.spark.scheduler.TaskSetManager:66 - Lost task 0.0 in stage 17.0 (TID 33, kl1393d4.is.klmcorp.net): java.io.InvalidClassException: com.klm.flight720.dataSources.ph.PhBoardingStatus$Closed$; no valid constructor
	at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:150)
	at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:790)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1775)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
	at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2000)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1924)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
	at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2000)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1924)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
	at org.apache.spark.serializer.JavaDeserializationStream.readObject(JavaSerializer.scala:75)
	at org.apache.spark.serializer.DeserializationStream.readValue(Serializer.scala:159)
	at org.apache.spark.serializer.DeserializationStream$$anon$2.getNext(Serializer.scala:189)
	at org.apache.spark.serializer.DeserializationStream$$anon$2.getNext(Serializer.scala:186)
        ...

I assume this is because our abstract class only has a cosntructor with 1 argument, and no no-args constructor. Any idea how I can solve this?

Missing EnumValues in IntEnumEntry

In my own code i got a NullPointerException -> before i noticed that some values in IntEnumEntry do not get initialized.

  • Am I doing something wrong ?
  • Or is this intended behavior ?

In the following example:

  • rightS seems to to be initialized just fine
  • while leftS -> is null

Reproducible code:

import enumeratum.values.{IntEnum, IntEnumEntry}
import scala.collection.immutable.IndexedSeq

sealed abstract class CPT(val value: Int, val leftS: CPT, val rightS: CPT) extends IntEnumEntry {
	val left = Option(leftS)
	val right = Option(rightS)
}

case object CPT extends IntEnum[CPT] {
	val values: IndexedSeq[CPT] = findValues

	case object S extends CPT(0, leftS = null, rightS = D)

	case object H extends CPT(1, leftS = null, rightS = null)

	case object D extends CPT(2, leftS = S, rightS = null)

}
import org.scalatest.FunSuite
import scala.collection.immutable

class TestExample extends FunSuite {

	test("test") {
		assert(CPT.S.right.contains(CPT.D))
		assert(CPT.D.left.contains(CPT.S))
	}
}

error message:

None did not contain S
ScalaTestFailureLocation: game.TestExample at (TestExample.scala:10)
org.scalatest.exceptions.TestFailedException: None did not contain S
at org.scalatest.Assertions.newAssertionFailedException(Assertions.scala:528)

Sleeker API

As raised in #109, it might be a good idea to brainstorm some ways to make Enumeratum's API sleeker and nicer to use in the future.

Some unfiltered thoughts:

  • Let's not worry about breaking backwards compatibility for now.
  • Let's aim at using Scalameta for future-proofing and the prospect of better IDE compatibility
  • Practically speaking, my experience is that the size and complexity of the macro grows non-linearly with every feature (language level) you want to support. The difficulty balloons even more when concise syntax is also a goal.
  • API should be simple and easy to understand

@daniel-shuy has submitted the following design:

@Enum(CapitalWords)
sealed abstract class Grocery(
    @EnumKey(unique = true)    // should unique default to true/false?
    id: Int, 

    @EnumKey(unique = false)    // should unique default to true/false?
    category: String,

    exampleBool: Boolean
)

object Grocery {
    case object Chicken extends Grocery(101, "Meat", true)
    case object Lamb extends Grocery(102, "Meat", false)
    // case object Pork extends Grocery(102, "Meat", true) <-- will fail to compile because the id 102 must be unique
    case object Coke extends Grocery(201, "Beverage", false)
}

would expand to:

sealed abstract class Grocery(id: Int, category: String, exampleBool: Boolean) extends EnumEntry with CapitalWords

object Grocery extends Enum[Grocery] {
    case object Chicken extends Grocery(101, "Meat")
    case object Lamb extends Grocery(102, "Meat")
    case object Coke extends Grocery(201, "Beverage")

    val values: immutable.IndexedSeq[Grocery] = IndexedSeq(Chicken, Lamb, Coke)
    private val idToEntriesMap: immutable.Map[Int, Grocery] = 
        Map(
            Chicken.id -> Chicken,
            Lamb.id -> Lamb,
            Coke.id -> Coke
        )
    private val categoryToEntriesMap: immutable.Map[String, Set[Grocery]] = ???    // some MultiMap implementation (Scala's MultiMap seems lacking...maybe Google Guava's HashMultimap?)

    def withId(id: Int): Grocery = idToEntriesMap(id)
    def withCategory(category: String): Set[Grocery] = categoryToEntriesMap(category)
}

Ordinal on entries

It would be nice if there were extra features on the entries like in Java enums to get the name and ordinal as well as the ability to get the enum by index.

Skinny-orm/scalike-jdbc support

just discovered this project today, and was blown away, i've been looking for something like this for a long time! noticed you mentioned slick support, so figured i'd try to get a scalike-jdbc/skinny-orm story going too. i basically have this

class Model(specialType: Option[SpecialType]) extends SkinnyRecord[Model]
object Model extends SkinnyCRUDMapper[Model]
sealed trait SpecialType extends EnumEntry

object SpecialType extends CirceEnum[SpecialType] with Enum[SpecialType] {
  val values = findValues

  case object Free extends SpecialType

  case object Beer extends SpecialType

  // cause i'll take FreeBeer over FooBar any day :)

  implicit val specialTypeBinders: Binders[SpecialType] = Binders.string.xmap(
    Option(_).map(SpecialType.withName).orNull,
    _.entryName
  )

  implicit val freeParameterBinderFactory: ParameterBinderFactory[Free.type] = ParameterBinderFactory {
    value => (stmt, idx) => stmt.setString(idx, value.entryName)
  }

  implicit val beerParameterBinderFactory: ParameterBinderFactory[Beer.type] = ParameterBinderFactory {
    value => (stmt, idx) => stmt.setString(idx, value.entryName)
  }
}

then i can do scalike-jdbc things like

Model.findBy(sqls.eq(Model.column.`specialType`, SpectialType.Beer))

ideally i'd woudn't need the freeParameterBinderFactory and beerParameterBinderFactory implicits, but if i take those out, then i have to do

Model.findBy(sqls.eq(Model.column.`specialType`, SpectialType.Beer.asInstanceOf[SpecialType]))

this might be more in scalike-jdbc's court (will raise an issue there too). but in case you know a solution/have a readme update to address it, figured i'd mention it here.
Most importantly, Thanks million for doing this project!

Nested ValueEnums don't work

I'm converting some of my stuff to the StringEnumEntry, (Thanks!) and I'm noticing a few things I did earlier (With stock EnumEntry) that don't work with this. It's quite possible the answers will be "don't do that".

I'd set up an object with all the objects associated with a use case. This allowed a single easy import statement to get all the relevant symbols. This doesn't compile:

sealed abstract class ParameterName(val value: String) extends StringEnumEntry
object ParameterConstants extends StringEnum[ParameterName] {
  object FOO extends ParameterName("foo")

  override def values = findValues

  sealed abstract class FooValue(val value: String) extends StringEnumEntry
  object FooValues extends StringEnum[FooValue] {
    object A extends FooValue("a")
    object B extends FooValue("b")

    override def values = findValues
  }

}

NullPointerException during JSON deserialization

I'm seeing this very hard to reproduce NullPointerException when I'm trying to deserialize JSON with play-json into an enumeratum enum.

java.lang.NullPointerException: null
at enumeratum.Enum$$anonfun$lowerCaseNamesToValuesMap$1.apply(Enum.scala:47)
at enumeratum.Enum$$anonfun$lowerCaseNamesToValuesMap$1.apply(Enum.scala:47)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:234)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:234)
at scala.collection.Iterator$class.foreach(Iterator.scala:893)
at scala.collection.AbstractIterator.foreach(Iterator.scala:1336)
at scala.collection.IterableLike$class.foreach(IterableLike.scala:72)
at scala.collection.AbstractIterable.foreach(Iterable.scala:54)
at scala.collection.TraversableLike$class.map(TraversableLike.scala:234)
at scala.collection.AbstractTraversable.map(Traversable.scala:104)
at enumeratum.Enum$class.lowerCaseNamesToValuesMap(Enum.scala:47)

It appears the the value of lowerCaseNamesToValuesMap is null. My guess is that it is connected to the variable being declared lazy.

If I access the variable lowerCaseNamesToValuesMap before the deserilisation the problem goes away. I used to only see this in test code but now also in production code.

Have you seen anything like this before?

Circe Enumeratum knownDirectSubclasses compile time error

Error during compilation of a CirceEnum: knownDirectSubclasses observed before subclass registered

Using:

  • Scala - 2.12.1
  • enumeratum - 1.5.3
  • enumeratum-circe - 1.5.3

Enumeratum ADT looks like:

import enumeratum._
sealed trait Role extends EnumEntry
case object Role extends CirceEnum[Role] with Enum[Role] {
  case object Service extends Role
  case object User extends Role
  val values = findValues
}

For this case, two errors are produced:

[error] knownDirectSubclasses of Role observed before subclass Service registered
[error] knownDirectSubclasses of Role observed before subclass User registered

Start building against 2.12

Scala 2.12 is going to come out fairly soon, so its probably a good idea to start building against the latest 2.12 milestone to figure out if there are any potential issues (there shouldn't be any, but who knows)

Java interop

Hi,

I'm having hard time figuring out how to use the enum values from Java code.

My enum looks like:

object Transfer {

  sealed trait Type extends EnumEntry
  object Type extends Enum[Type] {
    val values: immutable.IndexedSeq[Type] = findValues
    case object DEPOSIT extends Type
    case object WITHDRAWAL extends Type
  }

Intellij IDEA seems to be happy with Transfer.Type.DEPOSIT$.MODULE$ but scalac says: Error:(415, 89) java: incompatible types: enumeratum.EnumEntry cannot be converted to com.example.Transfer.Type

Any idea whether this can be achieved somehow ?

Scala: 2.12.4
Intellij IDEA: 2017.3.2
Enumeratum: 1.5.12

LowerCamelcase doesn't work

I'm using version "com.beachape" %% "enumeratum" % "1.5.12" with Scala 2.12, and LowerCamelcase prints the names with everything lowercase, no capitals at all.

Given the following example, I would expect howAreYou to be printed, but what's printed is howareyou:

import enumeratum.{Enum, EnumEntry}
import enumeratum.EnumEntry.LowerCamelcase

import scala.collection.immutable.IndexedSeq

sealed trait Thing extends EnumEntry with LowerCamelcase
object Thing extends Enum[Thing] {
  val values: IndexedSeq[Thing] = findValues
  case object HowAreYou extends Thing

}

println(Thing.HowAreYou.entryName)

Somehow fail when you're using object instead of case object

When you're using

sealed trait Foo extends EnumEntry
object Bar extends Enum[Foo] {
  val values = findValues
  object Baz extends Foo
}

You'll just get garbage as the name, without any compiler error:

@ Bar.Baz 
res3: Bar.Baz.type = $sess.cmd2$Bar$Baz$@71ac5723

Non-literal values for ValueEnums don't work.

I'm converting some of my stuff to the StringEnumEntry, (Thanks!) and I'm noticing a few things I did earlier (With stock EnumEntry) that don't work with this. It's quite possible the answers will be "don't do that".

I'd set up an intermediate, non-sealed Entry-type class to attach some common methods to. The following doesn't compile:

abstract class StringEntryWithHelpers(override val value: String) extends StringEnumEntry {
  //...
}


sealed abstract class Parameter(name: String) extends StringEntryWithHelpers(name)
object Parameters extends StringEnum[Parameter] {
  object FOO extends Parameter("foo")

  override def values = findValues
}

EnumSet/EnumMap

Continuing with my original purpose from #109,

I wanted to write an efficient EnumSet/EnumMap for Enumeratum like Java's.

Java's EnumSet is essentially a bitmap. EnumSet.contains() uses the index of the Enum entry to perform a bitwise AND operation on the bitmap.

Java's EnumMap is essentially an array that stores each Value in the array index corresponding to the index of the Enum entry.

In both cases, to replicate the same behavior, given just an instance of EnumEntry, the Set/Map needs to be able to obtain the index of the EnumEntry. Currently, EnumEntry does not know what its index is.

I've been trying all sorts of ways to pass the index to each instance of EnumEntry without breaking encapsulation. Ideally, if possible, I wanted to do so without changing the way EnumEntry/Enum is used. Since Enum has valuesToIndex, one of the easiest ways to achieve this is to somehow pass the object extending Enum to the sealed trait/abstract class extending EnumEntry.

To avoid changing the way EnumEntry is used, initially I did:

abstract class EnumEntry()(implicit enum: Enum[_]) {

but somehow it didn't work. Even if it did, if there were multiple Enums in the same file, would the implicit enum parameter look-up the wrong Enum object instance?
I then tried:

abstract class EnumEntry[E <: EnumEntry]()(implicit enum: Enum[E]) {

which requires the type of the sealed trait/abstract class to be specified as a Type Parameter, eg.

sealed trait MyEnum extends EnumEntry[MyEnum]

I then tried to enforce the Type Parameter passed in to be the sealed trait/abstract class instance, and tried to allow the compiler to automatically infer the Type Parameter:

abstract class EnumEntry[+Self <: EnumEntry]()(implicit enum: Enum[Self]) {

or

abstract class EnumEntry[E <: EnumEntry]()(implicit enum: Enum[E]) {
    self: E =>

which can then be used as:

sealed abstract class MyEnum extends EnumEntry    // must be abstract class to infer Self

which would achieve what I want, but unfortunately, I get weird errors when compiling the tests:
error: not found: value <none>

which I can't for the life of me figure out the meaning of.

@lloydmeta Your previous suggestion:

Welcome to the Ammonite Repl 0.8.1
(Scala 2.12.1 Java 1.8.0_101)
@ import $ivy.`com.beachape::enumeratum:1.5.7`, enumeratum._ 
import $ivy.$                               , enumeratum._
@ {
  // Declare 2 enums
  
  sealed trait AgeGroup extends EnumEntry
  
  case object AgeGroup extends Enum[AgeGroup] {
  
    val values = findValues
  
    case object Baby     extends AgeGroup
    case object Toddler  extends AgeGroup
    case object Teenager extends AgeGroup
    case object Adult    extends AgeGroup
    case object Senior   extends AgeGroup
  
  }
  
  sealed abstract class Light extends EnumEntry
  
  case object Light extends Enum[Light] {
    val values = findValues
    case object Red extends Light
    case object Blue extends Light
    case object Green extends Light
  }
  } 
defined trait AgeGroup
defined object AgeGroup
defined class Light
defined object Light
@ {
  // indexOf for any properly-implemented Enum
  def indexOf[A <: EnumEntry: Enum](entry: A): Int  = {
      implicitly[Enum[A]].indexOf(entry)
  }
  } 
defined function indexOf
@ indexOf(Light.Red: Light) 
res3: Int = 0
@  
@   indexOf(AgeGroup.Baby: AgeGroup) 
res4: Int = 0
@  
@   indexOf(AgeGroup.Adult: AgeGroup) 
res5: Int = 3

BTW, you'll notice that I had to add a type annotation when passing in each member; this is because otherwise, the value passed to the function will have the singleton type (e.g. AgeGroup.Adult.type instead of AgeGroup), which I suspect is what caused the error: not found: value error you saw :)

led me in the right direction, but I still can't find a way for EnumEntry to reference the Enum object without breaking backwards compatibility.

The reason I need to do this is because, the entire purpose of creating an efficient EnumSet/EnumMap is to avoid a Hash lookup (of a HashSet[EnumEntry]/HashMap[EnumEntry]).

Currently, Enum.indexOf() performs a Hash lookup using valuesToIndex, which defeats the purpose of creating an EnumSet/EnumMap that avoids the Hash lookup of a HashSet/HashMap. For this to work, each EnumEntry instance will need to cache its index, i.e:

abstract class EnumEntry {
    ...
    val index: Int = ???
    ...
}
trait Enum[A <: EnumEntry] {
    ...
    def indexOf(member: A): Int = A.index
    ...
}

I've tried:

val index: Int = Enum.materializeEnum[this.type].indexOf(this)
[error]  found   : enumeratum.EnumEntry.type
[error]  required: enumeratum.Enum[EnumEntry.this.type]

and various other ways involving implicits/generics/context bound/etc., but it all comes back to the same issue: Scala keeps referring either to the case object or the EnumEntry class itself, instead of the sealed trait/abstract class. I'm actually surprised that Scala doesn't traverse up the inheritance tree to find implicits.

A few ugly workarounds I can think of (but rather not resort to):

  • Add index as a private[enumeratum] var in EnumEntry, then assign each in Enum's constructor.
  • Use valuesToIndex the first time Enum.indexOf() is called, then cache the index as a var in EnumEntry.

Quill integration

I have a suggestion to add a trait to automatically encode/decode EnumEntrys as Strings, using their entryName, when querying/inserting from/into a Database using Quill.

This is my current implementation:

import enumeratum.{Enum, EnumEntry}
import io.getquill.MappedEncoding

trait QuillEnum[A <: EnumEntry] {
  this: Enum[A] =>

  implicit lazy val enumEncoder: MappedEncoding[A, String] = MappedEncoding(_.entryName)

  implicit lazy val enumDecoder: MappedEncoding[String, A] = MappedEncoding(withName)
}

Null entry in the collection returned by findValues

Hi,

I found some strange behavior of the findValues method / macro.

I used a member of the companion object in the enum class itself and some instance of the enum as the default value for a member in another class. When I now use the values vector that is created by using findValues, it contains a null entry. This entry is always the enum instance that is used first in the program.
See the example below for code that provokes this behavior.

This does not occur when:

  • Accessing the values member at least once before using any enum instance.
  • Referencing to the member of the companion object from somewhere else than the enum class.
  • Referencing to a member of some other object in the enum class.

Library versions:

  • Scala 2.12.2
  • Enumeratum 2.12 1.5.12
  • Enumeratum Macros 2.12 1.5.9

Example
Just misses a main method that creates a new X and calls print on it.

import enumeratum._

class X {
  var myValue: MyEnum = MyEnum.B // This enum value will be null in the values vector
  var myValue2: MyEnum = MyEnum.C // The second usage doesn't alter the values vector

  def print = println(MyEnum.values) // Output 'Vector(A, null, C)'
}

sealed abstract class MyEnum extends EnumEntry {
  // Using a value from the companion object causes the bug
  val myValue = MyEnum.aValue
}

object MyEnum extends Enum[MyEnum] {

  val aValue = 42

  val values = findValues

  case object A extends MyEnum
  case object B extends MyEnum
  case object C extends MyEnum

}

Make enumeratum part of the SP (Scala Platform)

With the new Scala Platform being announced (see https://contributors.scala-lang.org/) we now have the ability to add libraries to a defined Scala platform.

Enumeratum fills a pretty core functionality (creating proper Enum's out of ADT's) which would conceivably be required by a lot of libraries (including one that I am writing right now). Its also probably the most well tested and used library in this specific area.

The only possible issues that I can envisage is what parts of enumeratum would be part of the Scala Platform. I envisage that only enumeratum core would be included (because there is a pretty strict requirement regarding transitive dependencies). If this was to go ahead (and its decided that only enumeratum-core would go into SP), we would probably have to

  • Add in MIMA to enumeratum-core to ensure binary compatibility
  • Change enumeratum-core's versioning scheme to be different from the various addons, as the enumeratum-core versioning scheme should reflect its own stability (i.e. enumeratum-core's version number shouldn't be bumped because a new version of enumeratum-circe is released which has no bearing on core)
  • May need to rename enumeratum-core to something else if we have versioning conflicts due to changing the versioning scheme
  • Possibly make enumartum-core its own actual project? Not sure if this is required.
  • All the various consequences of being part of the Scala platform (described on the link earlier)

What are your thoughts on this? I am also willing to do the heavy lifting if you generally agree with the proposal in general.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.