Концепция перечисления в Scala - Какой вариант взять?

Scala У программистов есть несколько опций при определении перечислений:

При исследовании лучших практик в отношении перечислений в Scala я наткнулся на сообщение Google под названием Enumerations should DIE, а также this, в котором выявлена ​​потенциальная проблема с использованием класса Scala Enumeration. Обе эти ссылки внесли отрицательную тень над классом Scala Enumeration.

Вариант 2 выглядит как большая работа, а в отношении варианта 3 я еще не использовал библиотеку Scalaz, поэтому мне было бы интересно узнать, что у других было с помощью Scalaz Enum. Последний вариант заключается в взаимодействии с Java, который я стараюсь избегать, поскольку мне нравится использовать пуристский подход в моем программировании Scala.

Пункт этого сообщения состоит в том, чтобы использовать опыт сообщества, чтобы детализировать контекст (ы), когда один из вариантов был бы предпочтительнее другого, а также в каком контексте (-ах) использовал бы конкретный вариант, который был бы неправильным или может вызвать серьезные проблемы, чтобы можно было принять обоснованное решение при выборе одного варианта над другим. Я не ищу мнения, а скорее конкретный контекст использования, когда один вариант лучше, чем другой (ы); мнения, скорее всего, закрывают этот пост, поэтому, пожалуйста, избегайте этого.

Ответы

Ответ 1

Scala Перечисление

Преимущество Scala перечислений состоит в том, что они легкие. Если все, что вам нужно, это набор упорядоченных значений, это, вероятно, будет лучшим выбором. Однако есть два основных недостатка. Во-первых, трудно добавить к ним поведение; их основная цель - просто существовать как уникальный экземпляр, а не обеспечивать сложную функциональность. Во-вторых, у них есть стирание типа. Вторая ссылка показывает, как перегрузка методов с ними не работает.

Запечатанные объекты корпуса

Объекты Case - это другой конец спектра. Каждый из них является его собственным классом, что означает отсутствие стирания типа, и они могут обеспечить уникальное, сложное поведение. Недостатки - это высокие накладные расходы, поскольку каждый из них является собственным классом и отсутствие встроенной итерации над ними. Это хороший выбор, если вы хотите указать уникальные поля/методы/реализации для некоторых или всех экземпляров. Они также хорошо подходят для match s, но не для повторения всех экземпляров.

Перечисление Java

Что-то среднее. Добавление методов/полей к самому перечислению очень просто, но настройка поведения для отдельных экземпляров намного сложнее. Все содержится в одном классе, поэтому он имеет меньше накладных расходов, чем объекты case, и нет такой же проблемы с стиранием типа, как в Scala перечислениях. Если имеется упорядоченный, повторяющийся список значений, важно, и вы хотите добавить некоторые дополнительные функции, общие для всех экземпляров, они работают хорошо. Они также полезны, если стирание типа будет проблемой (например, если вы планируете отправлять функции, основанные на типе). Наконец, они гарантируют тривиальный переход с Java.

Не используя ни одну из библиотек, которые вы упомянули, я ничего не скажу о них.

Резюме

ScalaEnum

  • Iterable
  • Низкие накладные расходы

Запечатанные объекты корпуса

  • Уникальные поля и методы для каждого экземпляра

Java Enum

  • Легко реализуемые общие поля и методы
  • Iterable
  • Простой Java-интерфейс
  • Низкие накладные расходы

Ответ 2

В прошлом я использовал оба первых параметра в зависимости от обстоятельств. Я не могу говорить о других вариантах, но я бы не хотел использовать Java Enums. Обычно я всегда предпочитаю решение Scala над Java-решением, где он доступен. Я также не хотел бы вводить библиотеку только для перечислений. Кажется немного тяжелым представить большую библиотеку для выполнения такой небольшой задачи, особенно когда есть встроенные способы решения этой задачи. Это может быть иначе, если бы библиотека предлагала другие функции, которые я хотел, которые не были встроены.

Отчасти это зависит от того, что вам нужно для перечисления. Если вам нужно просто создать набор дискретных значений, я бы склонялся к варианту 2. Это действительно не так много работы. Вы можете сделать его более сложным, если этого требуют ваши потребности, но самый простой сценарий:

trait MyEnum
case object MyValue1 extends MyEnum
case object MyValue2 extends MyEnum

Если, с другой стороны, вам нужно что-то, что на самом деле предоставляет вам "упорядоченный" набор дискретных значений, которые вы можете перебирать, получать числовые значения для и т.д., тогда я могу больше склониться к перечислению Scala.

Ответ 3

Scalaz Enum

  • Концепция перечисления в Scalaz моделируется на Haskell Enum
  • Предоставляет полезные операции над последовательно упорядоченными типами
  • Разработка богатых перечислений через закрытые классы классов
  • Итерации по этим закрытым классам классов безопасным образом
  • Требуется дополнительный анализ со стороны исполнителя, чтобы определить понятие порядка для типа, который требуется перечислить.
  • Требуется определить функции succ и pred
  • Требуется переопределить функцию order из класса типа order.

Пример

import scalaz.Ordering.{EQ, GT, LT}
import scalaz.{Enum, Ordering, Show}

sealed abstract class Coloring(val toInt: Int, val name: String)

object Coloring extends ColoringInstances {

  case object RED extends Coloring(1, "RED")

  case object BLUE extends Coloring(2, "BLUE")

  case object GREEN extends Coloring(3, "GREEN")

}

sealed abstract class ColoringInstances {

  import Coloring._

  implicit val coloringInstance: Enum[Coloring] with Show[Coloring] = new Enum[Coloring] with Show[Coloring] {

    def order(a1: Coloring, a2: Coloring): Ordering = (a1, a2) match {
      case (RED, RED) => EQ
      case (RED, BLUE | GREEN) => LT
      case (BLUE, BLUE) => EQ
      case (BLUE, GREEN) => LT
      case (BLUE, RED) => GT
      case (GREEN, RED) => GT
      case (GREEN, BLUE) => GT
      case (GREEN, GREEN) => EQ
    }

    def append(c1: Coloring, c2: => Coloring): Coloring = c1 match {
      case Coloring.RED => c2
      case o => o
    }

    override def shows(c: Coloring) = c.name

    def zero: Coloring = Coloring.RED

    def succ(c: Coloring) = c match {
      case Coloring.RED => Coloring.BLUE
      case Coloring.BLUE => Coloring.GREEN
      case Coloring.GREEN => Coloring.RED
    }

    def pred(c: Coloring) = c match {
      case Coloring.GREEN => Coloring.BLUE
      case Coloring.BLUE => Coloring.RED
      case Coloring.RED => Coloring.GREEN
    }

    override def max = Some(GREEN)

    override def min = Some(RED)

  }

}

Результат:

val f = Enum[Coloring]
println(f.fromToL(Coloring.RED, Coloring.GREEN))

res1 : List(RED, BLUE, GREEN)

Ответ 4

Вы также можете использовать Enumeratum. Описание взято из их документации:

Enumeratum - это надежная и мощная реализация перечисления для Scala, который предлагает исчерпывающие предупреждения о совпадении шаблонов, интеграции с популярные библиотеки Scala и идиоматическое использование, которое не нарушит ваши IDE. Он должен быть достаточно похож на Scala, встроенный в Enumeration to быть просты в использовании и понимать, предлагая большую гибкость, безопасности типов и более высоких значений перечисления без необходимости поддерживать собственный набор значений.