Случайные объекты и перечисления в Scala

Существуют ли рекомендации о том, когда использовать case-классы (или case-объекты) против расширения Enumeration в Scala?

Кажется, они предлагают некоторые из тех же преимуществ.

14 ответов

Решение

Одно большое отличие состоит в том, что Enumerationприходят с поддержкой для их создания из некоторых name Строка. Например:

object Currency extends Enumeration {
   val GBP = Value("GBP")
   val EUR = Value("EUR") //etc.
} 

Тогда вы можете сделать:

val ccy = Currency.withName("EUR")

Это полезно при желании сохранить перечисления (например, в базе данных) или создать их из данных, находящихся в файлах. Тем не менее, я нахожу в целом, что перечисления немного неуклюжи в Scala и имеют ощущение неуклюжего дополнения, поэтому я теперь склонен использовать case objects. case object более гибок, чем перечисление:

sealed trait Currency { def name: String }
case object EUR extends Currency { val name = "EUR" } //etc.

case class UnknownCurrency(name: String) extends Currency

Так что теперь у меня есть преимущество...

trade.ccy match {
  case EUR                   =>
  case UnknownCurrency(code) =>
}

Как отметил chaotic3quilibrium (с некоторыми исправлениями, облегчающими чтение):

Что касается паттерна "UnknownCurrency(code)", существуют другие способы обработки, не находящие строку кода валюты, кроме "разрыва" закрытого набора природы Currency тип. UnknownCurrency быть типом Currency Теперь можно проникнуть в другие части API.

Желательно выдвинуть это дело наружу Enumeration и заставить клиента иметь дело с Option[Currency] type, который бы четко указывал, что проблема действительно совпадает, и "побуждает" пользователя API разобраться в этом самому.

Чтобы уточнить другие ответы здесь, основные недостатки case objectс над Enumerationэто:

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

  2. Не могу легко создать экземпляр из сохраняемой ценности. Это также верно, но, за исключением случаев огромных перечислений (например, всех валют), это не представляет огромных накладных расходов.

ОБНОВЛЕНИЕ: Создано новое решение на основе макросов, которое намного превосходит решение, которое я обрисовал в общих чертах ниже. Я настоятельно рекомендую использовать это новое решение на основе макросов. И, похоже, планы Дотти сделают этот стиль решения enum частью языка. Whoohoo!

Резюме:
Есть три основных шаблона для попытки воспроизвести Java Enum в рамках проекта Scala. Два из трех шаблонов; напрямую используя Java Enum а также scala.Enumeration, не способны включить полное сопоставление с образцом в Scala. И третий; "запечатанный trait + case object", делает..., но имеет сложности инициализации класса / объекта JVM, приводящие к непоследовательному порядковому генерации индекса.

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

Подробности:
Вопрос, который вы задаете, довольно общий; "... когда использовать case классы objects против расширения [scala.]Enumeration "И оказывается, что существует МНОГИЕ возможные ответы, каждый ответ зависит от тонкостей конкретных требований проекта, которые у вас есть. Ответ может быть сокращен до трех основных шаблонов.

Для начала давайте удостоверимся, что мы работаем с той же базовой идеей, что и перечисление. Давайте определим перечисление в основном с точки зрения Enum предоставляется с Java 5 (1.5):

  1. Он содержит естественно упорядоченный закрытый набор именованных членов
    1. Есть фиксированное количество членов
    2. Члены естественно упорядочены и явно проиндексированы
      • В отличие от сортировки на основе некоторых критериев inate член
    3. Каждый участник имеет уникальное имя в общем наборе всех участников
  2. Все члены могут быть легко перебраны на основе их индексов
  3. Член может быть получен с его (чувствительным к регистру) именем
    1. Было бы неплохо, если бы член также мог быть извлечен с нечувствительным к регистру именем
  4. Член может быть восстановлен с его индексом
  5. Участники могут легко, прозрачно и эффективно использовать сериализацию
  6. Члены могут быть легко расширены для хранения дополнительных связанных одноэлементных данных
  7. Думать за пределами Java Enum было бы неплохо иметь возможность явно использовать проверку исчерпывающего соответствия шаблонов в Scala для перечисления

Далее давайте посмотрим на развернутые версии трех самых распространенных шаблонов решений:

А) На самом деле напрямую с использованием Java Enum шаблон (в смешанном проекте Scala/Java):

public enum ChessPiece {
    KING('K', 0)
  , QUEEN('Q', 9)
  , BISHOP('B', 3)
  , KNIGHT('N', 3)
  , ROOK('R', 5)
  , PAWN('P', 1)
  ;

  private char character;
  private int pointValue;

  private ChessPiece(char character, int pointValue) {
    this.character = character; 
    this.pointValue = pointValue;   
  }

  public int getCharacter() {
    return character;
  }

  public int getPointValue() {
    return pointValue;
  }
}

Следующие элементы из определения перечисления недоступны:

  1. 3.1 - Было бы неплохо, если бы член также мог быть извлечен с нечувствительным к регистру именем
  2. 7. Думая не только о Enum в Java, было бы неплохо иметь возможность явно использовать проверку исчерпывающего соответствия шаблонов в Scala для перечисления

Для моих текущих проектов у меня нет преимущества рисковать по пути смешанного проекта Scala/Java. И даже если бы я мог сделать смешанный проект, пункт 7 крайне важен для того, чтобы позволить мне улавливать проблемы времени компиляции, если / когда я либо добавляю / удаляю членов перечисления, либо пишу какой-то новый код для работы с существующими членами перечисления.


Б) Используя " sealed trait + case objects " шаблон:

sealed trait ChessPiece {def character: Char; def pointValue: Int}
object ChessPiece {
  case object KING extends ChessPiece {val character = 'K'; val pointValue = 0}
  case object QUEEN extends ChessPiece {val character = 'Q'; val pointValue = 9}
  case object BISHOP extends ChessPiece {val character = 'B'; val pointValue = 3}
  case object KNIGHT extends ChessPiece {val character = 'N'; val pointValue = 3}
  case object ROOK extends ChessPiece {val character = 'R'; val pointValue = 5}
  case object PAWN extends ChessPiece {val character = 'P'; val pointValue = 1}
}

Следующие элементы из определения перечисления недоступны:

  1. 1.2 - Члены естественно упорядочены и явно проиндексированы
  2. 2 - Все члены могут быть легко перебраны на основе их индексов
  3. 3 - член может быть найден с его (чувствительным к регистру) именем
  4. 3.1 - Было бы неплохо, если бы член также мог быть извлечен с нечувствительным к регистру именем
  5. 4 - член может быть найден с его индексом

Можно утверждать, что он действительно соответствует пунктам 5 и 6 определения перечисления. Для 5 сложно утверждать, что он эффективен. Для 6 не так-то просто расширить для хранения дополнительных связанных данных одиночности.


В) Использование scala.Enumeration шаблон (вдохновленный этим ответом Stackru):

object ChessPiece extends Enumeration {
  val KING = ChessPieceVal('K', 0)
  val QUEEN = ChessPieceVal('Q', 9)
  val BISHOP = ChessPieceVal('B', 3)
  val KNIGHT = ChessPieceVal('N', 3)
  val ROOK = ChessPieceVal('R', 5)
  val PAWN = ChessPieceVal('P', 1)
  protected case class ChessPieceVal(character: Char, pointValue: Int) extends super.Val()
  implicit def convert(value: Value) = value.asInstanceOf[ChessPieceVal]
}

Следующие элементы из определения перечисления недоступны (оказывается, идентичны списку для непосредственного использования Enum Java):

  1. 3.1 - Было бы неплохо, если бы член также мог быть извлечен с нечувствительным к регистру именем
  2. 7. Думая не только о Enum в Java, было бы неплохо иметь возможность явно использовать проверку исчерпывающего соответствия шаблонов в Scala для перечисления

Опять же, для моих текущих проектов пункт 7 важен для того, чтобы я мог уловить проблемы времени компиляции, если / когда я либо добавляю / удаляю членов перечисления, либо пишу какой-то новый код для работы с существующими членами перечисления.


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

  1. Java Enum непосредственно в смешанном проекте Scala/Java
  2. "Запечатанная черта + тематические объекты"
  3. scala.Enumeration

Каждое из этих решений может быть в конечном счете переработано / расширено / реорганизовано, чтобы попытаться покрыть некоторые из недостающих требований каждого из них. Тем не менее, ни Java Enum ни scala.Enumeration Решения могут быть достаточно расширены, чтобы обеспечить пункт 7. А для моих собственных проектов это одна из наиболее убедительных ценностей использования закрытого типа в Scala. Я настоятельно предпочитаю, чтобы предупреждения / ошибки во время компиляции указывали на то, что в моем коде есть пробел / проблема, а не необходимость выявлять его из исключения / сбоя производственной среды выполнения.


В связи с этим я приступил к работе с case object путь, чтобы увидеть, смогу ли я найти решение, которое охватывало бы все перечисленные выше определения. Первая задача состояла в том, чтобы протолкнуть ядро ​​проблемы инициализации класса / объекта JVM (подробно описано в этом посте Stackru). И я наконец смог найти решение.

Поскольку мое решение - две черты; Перечисление и Перечень Украшенные, а так как Enumeration trait больше +400 строк (много комментариев, объясняющих контекст), я отказываюсь вставлять его в эту ветку (что заставило бы его растягивать страницу). Для получения подробной информации, пожалуйста, перейдите прямо к Gist.

Вот как выглядит решение, используя ту же идею данных, что и выше (полностью закомментированную версию, доступную здесь), и реализованную в EnumerationDecorated,

import scala.reflect.runtime.universe.{TypeTag,typeTag}
import org.public_domain.scala.utils.EnumerationDecorated

object ChessPiecesEnhancedDecorated extends EnumerationDecorated {
  case object KING extends Member
  case object QUEEN extends Member
  case object BISHOP extends Member
  case object KNIGHT extends Member
  case object ROOK extends Member
  case object PAWN extends Member

  val decorationOrderedSet: List[Decoration] =
    List(
        Decoration(KING,   'K', 0)
      , Decoration(QUEEN,  'Q', 9)
      , Decoration(BISHOP, 'B', 3)
      , Decoration(KNIGHT, 'N', 3)
      , Decoration(ROOK,   'R', 5)
      , Decoration(PAWN,   'P', 1)
    )

  final case class Decoration private[ChessPiecesEnhancedDecorated] (member: Member, char: Char, pointValue: Int) extends DecorationBase {
    val description: String = member.name.toLowerCase.capitalize
  }
  override def typeTagMember: TypeTag[_] = typeTag[Member]
  sealed trait Member extends MemberDecorated
}

Это пример использования новой пары перечисленных черт, которые я создал (находится в этом разделе) для реализации всех возможностей, желаемых и описанных в определении перечисления.

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

  1. Инициализация объекта / класса JVM для этой конкретной модели объекта / случая не определена (см. Этот поток Stackru)
  2. Содержимое, возвращаемое методом getClass.getDeclaredClasses имеет неопределенный порядок (и вряд ли он будет в том же порядке, что и case object объявления в исходном коде)

Учитывая эти две проблемы, мне пришлось отказаться от попыток сгенерировать подразумеваемое упорядочение, и мне пришлось явно требовать, чтобы клиент определил и объявил его с каким-то понятием упорядоченного набора. Поскольку коллекции Scala не имеют реализации упорядоченного набора вставок, лучшее, что я мог сделать, это использовать List а затем во время выполнения проверьте, что это действительно набор. Это не то, как я бы предпочел добиться этого.

И, учитывая дизайн, этот второй список / установить порядок val, Учитывая ChessPiecesEnhancedDecorated пример выше, можно было добавить case object PAWN2 extends Member а потом забудьте добавить Decoration(PAWN2,'P2', 2) в decorationOrderedSet, Итак, есть проверка во время выполнения, чтобы убедиться, что список не только набор, но и содержит ВСЕ объекты case, которые расширяют sealed trait Member, Это была особая форма отражения / макро-ада, через которую нужно проработать.


Пожалуйста, оставляйте комментарии и / или отзывы о Gist.

Объекты Case уже возвращают свое имя для своих методов toString, поэтому передавать его отдельно не нужно. Вот версия, аналогичная jho (удобные методы для краткости опущены):

trait Enum[A] {
  trait Value { self: A => }
  val values: List[A]
}

sealed trait Currency extends Currency.Value
object Currency extends Enum[Currency] {
  case object EUR extends Currency
  case object GBP extends Currency
  val values = List(EUR, GBP)
}

Объекты ленивы; используя вместо этого vals, мы можем удалить список, но должны повторить имя:

trait Enum[A <: {def name: String}] {
  trait Value { self: A =>
    _values :+= this
  }
  private var _values = List.empty[A]
  def values = _values
}

sealed abstract class Currency(val name: String) extends Currency.Value
object Currency extends Enum[Currency] {
  val EUR = new Currency("EUR") {}
  val GBP = new Currency("GBP") {}
}

Если вы не возражаете против обмана, вы можете предварительно загрузить значения перечисления, используя API отражений или что-то вроде Google Reflections. Не ленивые объекты case дают вам самый чистый синтаксис:

trait Enum[A] {
  trait Value { self: A =>
    _values :+= this
  }
  private var _values = List.empty[A]
  def values = _values
}

sealed trait Currency extends Currency.Value
object Currency extends Enum[Currency] {
  case object EUR extends Currency
  case object GBP extends Currency
}

Хороший и чистый, со всеми преимуществами case-классов и перечислений Java. Лично я определяю значения перечисления вне объекта, чтобы лучше соответствовать идиоматическому коду Scala:

object Currency extends Enum[Currency]
sealed trait Currency extends Currency.Value
case object EUR extends Currency
case object GBP extends Currency

Преимущества использования case-классов перед перечислениями:

  • При использовании запечатанных падежных классов компилятор Scala может определить, полностью ли найдено совпадение, например, когда все возможные совпадения указаны в объявлении сопоставления. С перечислениями компилятор Scala не может сказать.
  • Классы Case, естественно, поддерживают больше полей, чем перечисление на основе значений, которое поддерживает имя и идентификатор.

Преимущества использования перечислений вместо case-классов:

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

Так что, в общем, если вам просто нужен список простых констант по имени, используйте перечисления. В противном случае, если вам нужно что-то более сложное или вы хотите, чтобы компилятор сообщал вам, что у вас есть все совпадения, используйте классы case.

ОБНОВЛЕНИЕ: код ниже имеет ошибку, описанную здесь. Приведенная ниже тестовая программа работает, но если бы вы использовали DayOfWeek.Mon (например) перед самим DayOfWeek, то она не состоялась бы, потому что DayOfWeek не был инициализирован (использование внутреннего объекта не вызывает инициализацию внешнего объекта). Вы все еще можете использовать этот код, если вы делаете что-то вроде val enums = Seq( DayOfWeek ) в вашем основном классе, вызывая инициализацию ваших перечислений, или вы можете использовать модификации chaotic3quilibrium. С нетерпением ждем перечисления на основе макросов!


Если ты хочешь

  • предупреждения о неисчерпывающих совпадениях с образцами
  • Int ID, назначенный каждому значению перечисления, которым вы можете управлять
  • неизменный список значений перечисления в порядке их определения
  • неизменяемая карта от имени к перечисляемому значению
  • неизменяемая карта от id до значения enum
  • места для размещения методов / данных для всех или определенных значений перечисления, или для перечисления в целом
  • упорядоченные значения перечисления (так что вы можете проверить, например, день <среда)
  • способность расширять одно перечисление для создания других

тогда может представлять интерес следующее. Обратная связь приветствуется.

В этой реализации есть абстрактные базовые классы Enum и EnumVal, которые вы расширяете. Мы увидим эти классы через минуту, но сначала вот как вы определите перечисление:

object DayOfWeek extends Enum {
  sealed abstract class Val extends EnumVal
  case object Mon extends Val; Mon()
  case object Tue extends Val; Tue()
  case object Wed extends Val; Wed()
  case object Thu extends Val; Thu()
  case object Fri extends Val; Fri()
  case object Sat extends Val; Sat()
  case object Sun extends Val; Sun()
}

Обратите внимание, что вы должны использовать каждое значение перечисления (вызвать его метод apply), чтобы воплотить его в жизнь. [Я бы хотел, чтобы внутренние объекты не были ленивыми, если я специально не попросил их об этом. Я думаю.]

Конечно, мы можем добавить методы / данные в DayOfWeek, Val или отдельные объекты case, если мы того пожелаем.

А вот как вы могли бы использовать такое перечисление:

object DayOfWeekTest extends App {

  // To get a map from Int id to enum:
  println( DayOfWeek.valuesById )

  // To get a map from String name to enum:
  println( DayOfWeek.valuesByName )

  // To iterate through a list of the enum values in definition order,
  // which can be made different from ID order, and get their IDs and names:
  DayOfWeek.values foreach { v => println( v.id + " = " + v ) }

  // To sort by ID or name:
  println( DayOfWeek.values.sorted mkString ", " )
  println( DayOfWeek.values.sortBy(_.toString) mkString ", " )

  // To look up enum values by name:
  println( DayOfWeek("Tue") ) // Some[DayOfWeek.Val]
  println( DayOfWeek("Xyz") ) // None

  // To look up enum values by id:
  println( DayOfWeek(3) )         // Some[DayOfWeek.Val]
  println( DayOfWeek(9) )         // None

  import DayOfWeek._

  // To compare enums as ordinals:
  println( Tue < Fri )

  // Warnings about non-exhaustive pattern matches:
  def aufDeutsch( day: DayOfWeek.Val ) = day match {
    case Mon => "Montag"
    case Tue => "Dienstag"
    case Wed => "Mittwoch"
    case Thu => "Donnerstag"
    case Fri => "Freitag"
 // Commenting these out causes compiler warning: "match is not exhaustive!"
 // case Sat => "Samstag"
 // case Sun => "Sonntag"
  }

}

Вот что вы получите, когда его скомпилируете:

DayOfWeekTest.scala:31: warning: match is not exhaustive!
missing combination            Sat
missing combination            Sun

  def aufDeutsch( day: DayOfWeek.Val ) = day match {
                                         ^
one warning found

Вы можете заменить "день совпадения" на "(день: @unchecked) совпадение", если вы не хотите таких предупреждений, или просто включить в конец универсальный случай.

Когда вы запускаете вышеуказанную программу, вы получаете такой вывод:

Map(0 -> Mon, 5 -> Sat, 1 -> Tue, 6 -> Sun, 2 -> Wed, 3 -> Thu, 4 -> Fri)
Map(Thu -> Thu, Sat -> Sat, Tue -> Tue, Sun -> Sun, Mon -> Mon, Wed -> Wed, Fri -> Fri)
0 = Mon
1 = Tue
2 = Wed
3 = Thu
4 = Fri
5 = Sat
6 = Sun
Mon, Tue, Wed, Thu, Fri, Sat, Sun
Fri, Mon, Sat, Sun, Thu, Tue, Wed
Some(Tue)
None
Some(Thu)
None
true

Обратите внимание, что поскольку List и Maps являются неизменяемыми, вы можете легко удалять элементы для создания подмножеств, не нарушая сам перечисление.

Вот сам класс Enum (и EnumVal внутри него):

abstract class Enum {

  type Val <: EnumVal

  protected var nextId: Int = 0

  private var values_       =       List[Val]()
  private var valuesById_   = Map[Int   ,Val]()
  private var valuesByName_ = Map[String,Val]()

  def values       = values_
  def valuesById   = valuesById_
  def valuesByName = valuesByName_

  def apply( id  : Int    ) = valuesById  .get(id  )  // Some|None
  def apply( name: String ) = valuesByName.get(name)  // Some|None

  // Base class for enum values; it registers the value with the Enum.
  protected abstract class EnumVal extends Ordered[Val] {
    val theVal = this.asInstanceOf[Val]  // only extend EnumVal to Val
    val id = nextId
    def bumpId { nextId += 1 }
    def compare( that:Val ) = this.id - that.id
    def apply() {
      if ( valuesById_.get(id) != None )
        throw new Exception( "cannot init " + this + " enum value twice" )
      bumpId
      values_ ++= List(theVal)
      valuesById_   += ( id       -> theVal )
      valuesByName_ += ( toString -> theVal )
    }
  }

}

А вот его более продвинутое использование, которое контролирует идентификаторы и добавляет данные / методы к абстракции Val и к самому перечислению:

object DayOfWeek extends Enum {

  sealed abstract class Val( val isWeekday:Boolean = true ) extends EnumVal {
    def isWeekend = !isWeekday
    val abbrev = toString take 3
  }
  case object    Monday extends Val;    Monday()
  case object   Tuesday extends Val;   Tuesday()
  case object Wednesday extends Val; Wednesday()
  case object  Thursday extends Val;  Thursday()
  case object    Friday extends Val;    Friday()
  nextId = -2
  case object  Saturday extends Val(false); Saturday()
  case object    Sunday extends Val(false);   Sunday()

  val (weekDays,weekendDays) = values partition (_.isWeekday)
}

У меня есть хорошая простая библиотека, которая позволяет вам использовать запечатанные черты / классы в качестве значений перечисления без необходимости вести собственный список значений. Он опирается на простой макрос, который не зависит от ошибки knownDirectSubclasses,

https://github.com/lloydmeta/enumeratum

Обновление марта 2017 года: как прокомментировал Anthony Accioly, scala.Enumeration/enum PR был закрыт.

Дотти (компилятор следующего поколения для Scala) возглавит, хотя дотти выпуск 1970 года и PR Мартина Одерского 1958 года.


Примечание: в настоящее время (август 2016 г., 6+ лет спустя) есть предложение удалить scala.Enumeration: PR 5352

протестовать scala.Enumeration, добавлять @enum аннотирование

Синтаксис

@enum
 class Toggle {
  ON
  OFF
 }

является возможным примером реализации, намерение также состоит в том, чтобы поддерживать ADT, которые соответствуют определенным ограничениям (без вложенности, рекурсии или изменяющихся параметров конструктора), например:

@enum
sealed trait Toggle
case object ON  extends Toggle
case object OFF extends Toggle

Устаревает безудержная катастрофа, которая scala.Enumeration,

Преимущества @enum перед scala.Enumeration:

  • На самом деле работает
  • Java-взаимодействие
  • Нет проблем с удалением
  • Не нужно вводить в заблуждение мини-DSL для изучения при перечислении

Недостатки: нет.

Это решает проблему невозможности иметь одну кодовую базу, которая поддерживает Scala-JVM, Scala.js и Scala-Native (исходный код Java не поддерживается на Scala.js/Scala-Native, Исходный код Scala не может определить перечисления, которые принимаются существующими API в Scala-JVM).

Еще один недостаток классов case по сравнению с перечислениями, когда вам нужно будет выполнять итерации или фильтровать все экземпляры. Это встроенная возможность Enumeration (и перечислений Java), в то время как case-классы автоматически не поддерживают такую ​​возможность.

Другими словами: "нет простого способа получить список общего набора перечисляемых значений с помощью case-классов".

Если вы серьезно относитесь к поддержанию взаимодействия с другими языками JVM (например, Java), тогда лучшим вариантом будет написание перечислений Java. Они работают прозрачно как из кода Scala, так и из кода Java, чего нельзя сказать о scala.Enumeration или тематические объекты. Давайте не будем иметь новую библиотеку перечислений для каждого нового хобби-проекта на GitHub, если этого можно избежать!

Я видел разные версии создания класса case, имитирующего перечисление. Вот моя версия:

trait CaseEnumValue {
    def name:String
}

trait CaseEnum {
    type V <: CaseEnumValue
    def values:List[V]
    def unapply(name:String):Option[String] = {
        if (values.exists(_.name == name)) Some(name) else None
    }
    def unapply(value:V):String = {
        return value.name
    }
    def apply(name:String):Option[V] = {
        values.find(_.name == name)
    }
}

Что позволяет вам создавать классы case, которые выглядят следующим образом:

abstract class Currency(override name:String) extends CaseEnumValue {
}

object Currency extends CaseEnum {
    type V = Site
    case object EUR extends Currency("EUR")
    case object GBP extends Currency("GBP")
    var values = List(EUR, GBP)
}

Может быть, кто-то может придумать лучший трюк, чем просто добавить класс каждого случая в список, как я. Это было все, что я мог придумать в то время.

Я предпочитаю case objects (это вопрос личных предпочтений). Чтобы справиться с проблемами, присущими этому подходу (анализ строки и итерация по всем элементам), я добавил несколько строк, которые не идеальны, но эффективны.

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

/**
 * Enum for Genre. It contains the type, objects, elements set and parse method.
 *
 * This approach supports:
 *
 * - Pattern matching
 * - Parse from name
 * - Get all elements
 */
object Genre {
  sealed trait Genre

  case object MALE extends Genre
  case object FEMALE extends Genre

  val elements = Set (MALE, FEMALE) // You have to take care this set matches all objects

  def apply (code: String) =
    if (MALE.toString == code) MALE
    else if (FEMALE.toString == code) FEMALE
    else throw new IllegalArgumentException
}

/**
 * Enum usage (and tests).
 */
object GenreTest extends App {
  import Genre._

  val m1 = MALE
  val m2 = Genre ("MALE")

  assert (m1 == m2)
  assert (m1.toString == "MALE")

  val f1 = FEMALE
  val f2 = Genre ("FEMALE")

  assert (f1 == f2)
  assert (f1.toString == "FEMALE")

  try {
    Genre (null)
    assert (false)
  }
  catch {
    case e: IllegalArgumentException => assert (true)
  }

  try {
    Genre ("male")
    assert (false)
  }
  catch {
    case e: IllegalArgumentException => assert (true)
  }

  Genre.elements.foreach { println }
}

Последние два раза я нуждался в этих двух вариантах. До недавнего времени я предпочитал опцию "Запечатанный объект / черта".

1) Счетная декларация Scala

object OutboundMarketMakerEntryPointType extends Enumeration {
  type OutboundMarketMakerEntryPointType = Value

  val Alpha, Beta = Value
}

2) Запечатанные Черты + Объекты Case

sealed trait OutboundMarketMakerEntryPointType

case object AlphaEntryPoint extends OutboundMarketMakerEntryPointType

case object BetaEntryPoint extends OutboundMarketMakerEntryPointType

Хотя ни один из них на самом деле не соответствует всем перечисленным Java-перечислениям, ниже приведены плюсы и минусы:

Scala Enumeration

Плюсы: -Функции для создания экземпляров с опцией или прямого предположения точного (проще при загрузке из постоянного хранилища) -Изменяется все возможные значения

Минусы: - Предупреждение компиляции для неисчерпывающего поиска не поддерживается (делает сопоставление с шаблоном менее идеальным)

Объекты Case / Запечатанные черты

Плюсы: -Используя запечатанные черты, мы можем предварительно создать некоторые значения, в то время как другие могут быть введены во время создания -Полная поддержка сопоставления с образцом (определены методы применения / отмены применения)

Минусы: - Создание экземпляров из постоянного хранилища - здесь часто приходится использовать сопоставление с образцом или определять собственный список всех возможных "значений перечисления".

Что в конечном итоге заставило меня изменить свое мнение, было что-то вроде следующего фрагмента:

object DbInstrumentQueries {
  def instrumentExtractor(tableAlias: String = "s")(rs: ResultSet): Instrument = {
    val symbol = rs.getString(tableAlias + ".name")
    val quoteCurrency = rs.getString(tableAlias + ".quote_currency")
    val fixRepresentation = rs.getString(tableAlias + ".fix_representation")
    val pointsValue = rs.getInt(tableAlias + ".points_value")
    val instrumentType = InstrumentType.fromString(rs.getString(tableAlias +".instrument_type"))
    val productType = ProductType.fromString(rs.getString(tableAlias + ".product_type"))

    Instrument(symbol, fixRepresentation, quoteCurrency, pointsValue, instrumentType, productType)
  }
}

object InstrumentType {
  def fromString(instrumentType: String): InstrumentType = Seq(CurrencyPair, Metal, CFD)
  .find(_.toString == instrumentType).get
}

object ProductType {

  def fromString(productType: String): ProductType = Seq(Commodity, Currency, Index)
  .find(_.toString == productType).get
}

.get вызовы были отвратительными - используя перечисление, я могу просто вызвать метод withName перечисления следующим образом:

object DbInstrumentQueries {
  def instrumentExtractor(tableAlias: String = "s")(rs: ResultSet): Instrument = {
    val symbol = rs.getString(tableAlias + ".name")
    val quoteCurrency = rs.getString(tableAlias + ".quote_currency")
    val fixRepresentation = rs.getString(tableAlias + ".fix_representation")
    val pointsValue = rs.getInt(tableAlias + ".points_value")
    val instrumentType = InstrumentType.withNameString(rs.getString(tableAlias + ".instrument_type"))
    val productType = ProductType.withName(rs.getString(tableAlias + ".product_type"))

    Instrument(symbol, fixRepresentation, quoteCurrency, pointsValue, instrumentType, productType)
  }
}

Поэтому я думаю, что в будущем я предпочитаю использовать Enumerations, когда значения предназначены для доступа из репозитория, а объекты case / запечатанные признаки - в противном случае.

Для тех, кто все еще ищет, как заставить работать ответ GatesDa: Вы можете просто ссылаться на объект case после того, как объявите его, чтобы создать его экземпляр:

trait Enum[A] {
  trait Value { self: A =>
    _values :+= this
  }
  private var _values = List.empty[A]
  def values = _values
}

sealed trait Currency extends Currency.Value
object Currency extends Enum[Currency] {
  case object EUR extends Currency; 
  EUR //THIS IS ONLY CHANGE
  case object GBP extends Currency; GBP //Inline looks better
}

Я думаю, что самое большое преимущество case classes над enumerations является то, что вы можете использовать шаблон класса типа, ака ad-hoc полиморфизм. Не нужно сопоставлять перечисления как:

someEnum match {
  ENUMA => makeThis()
  ENUMB => makeThat()
}

вместо этого у вас будет что-то вроде:

def someCode[SomeCaseClass](implicit val maker: Maker[SomeCaseClass]){
  maker.make()
}

implicit val makerA = new Maker[CaseClassA]{
  def make() = ...
}
implicit val makerB = new Maker[CaseClassB]{
  def make() = ...
}
Другие вопросы по тегам