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

211

Есть ли рекомендации по лучшей практике, когда следует использовать классы case (или объекты case) и расширение Enumeration в Scala?

Они, похоже, предлагают одни и те же преимущества.

  • 1
    Я написал небольшой обзор о перечислении scala и его альтернативах, вы можете найти его полезным: pedrorijo.com/blog/scala-enums/
Теги:
enumeration
case-class

14 ответов

218
Лучший ответ

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

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

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

val ccy = Currency.withName("EUR")

Это полезно, если вы хотите сохранить перечисления (например, в базе данных) или создать их из данных, находящихся в файлах. Тем не менее, я считаю, что перечисления немного неуклюжи в Scala и имеют ощущение неудобного дополнения, поэтому теперь я склонен использовать case object s. A 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], который будет четко указывать, что существует действительно соответствующая проблема и "поощрять" пользователя API к его сортировке.

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

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

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

  • 9
    Другое отличие состоит в том, что перечисление перечисления упорядочено из коробки, тогда как перечисление объекта на основе случая явно не
  • 1
    Еще один момент для объектов case - это если вы заботитесь о совместимости Java. Перечисление будет возвращать значения как Enumeration.Value, таким образом 1) требуется scala-библиотека, 2) потеря фактической информации о типе.
Показать ещё 2 комментария
65

Объекты 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. Объекты без ленивого случая дают вам самый чистый синтаксис:

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. Лично я определяю значения перечисления вне объекта для лучшего соответствия idiomatic Scala code:

object Currency extends Enum[Currency]
sealed trait Currency extends Currency.Value
case object EUR extends Currency
case object GBP extends Currency
  • 3
    один вопрос: последнее решение называется «объекты без ленивости», но в этом случае объекты не загружаются, пока мы их не используем: почему вы называете это решение не ленивым?
  • 0
    Я плотный, но копирование любого из этих 3-х примеров в дроссели repl на sealed trait Currency extends Currency.Value строку sealed trait Currency extends Currency.Value , давая :5: error: not found: value Currency Чего мне не хватает?
Показать ещё 7 комментариев
60

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

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

Я создал решение с двумя классами; Перечисление и перечисление Dececated, расположенные в этом Gist. Я не размещал код в этом потоке, так как файл 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 для перечисления

Затем давайте посмотрим на откидные версии трех наиболее распространенных шаблонов решений:

A) Фактически непосредственно используя шаблон 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 - Мышление за пределами Java Enum, было бы неплохо иметь возможность явно использовать проверку соответствия шаблону Scala для перечисления

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


B) Использование шаблона " 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 это не очень просто расширить, чтобы удерживать дополнительные связанные данные одиночной последовательности.


C) Использование шаблона scala.Enumeration (вдохновленный https://stackoverflow.com/questions/4346580/how-to-add-a-method-to-enumeration-in-scala):

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]
}

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

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

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


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

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

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


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

Поскольку мое решение - две черты; Enumeration и EnumerationDecorated, и поскольку свойство Enumeration превышает +400 строки long (много комментариев, объясняющих контекст), я отказываюсь вставлять их в этот поток (что сделало бы его растягивание вниз страницей). Для получения дополнительной информации перейдите прямо к 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
}

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

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

  1. Инициализация объекта/класса JVM для этой конкретной объектной модели объекта/объекта не определена (см. https://stackoverflow.com/questions/14947179/using-a-custom-enum-in-the-scala-worksheet-i-am-receiving-an-error-java-lang-ex)
  2. Содержимое, возвращаемое из метода getClass.getDeclaredClasses имеет неопределенный порядок (и это вряд ли будет в том же порядке, что и объявления case object в исходном коде)

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

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


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

  • 0
    Сейчас я выпустил первую версию библиотеки ScalaOlio (GPLv3), которая содержит более актуальные версии как org.scalaolio.util.Enumeration и org.scalaolio.util.EnumerationDecorated : scalaolio.org
  • 0
    И прыгать прямо в хранилище ScalaOlio на Github: github.com/chaotic3quilibrium/scala-olio
Показать ещё 2 комментария
26

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

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

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

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

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

15

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


Если вы хотите

  • предупреждения о не исчерпывающем шаблоне соответствуют
  • Идентификатор Int, присвоенный каждому значению перечисления, который вы можете контролировать
  • неизменный список значений перечисления в том порядке, в котором они были определены
  • неизменяемая Карта от имени до значения перечисления
  • неизменяемая Карта от 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

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

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

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

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

Вот сам класс 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)
}
  • 0
    Tyvm за предоставление этого. Я очень ценю это. Тем не менее, я замечаю, что он использует «var», а не val. И это пограничный смертный грех в мире FP. Итак, есть ли способ реализовать это так, чтобы не использовать var? Просто любопытно, если это какой-то крайний случай типа FP, и я не понимаю, почему ваша реализация нежелательна.
  • 2
    Я, вероятно, не могу вам помочь. В Scala довольно распространено написание классов, которые видоизменяются внутри, но являются неизменными для тех, кто их использует. В приведенном выше примере пользователь DayOfWeek не может изменить перечисление; например, невозможно изменить идентификатор вторника или его имя по факту. Но если вы хотите реализацию , которая свободна от мутации внутри, то я ничего не имею. Однако я не удивлюсь, увидев в 2.11 замечательную новую возможность enum, основанную на макросах; идеи бьют по скала-лангу.
Показать ещё 5 комментариев
11

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

https://github.com/lloydmeta/enumeratum

10

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

Dotty (компилятор следующего поколения для Scala) возьмет на себя инициативу, хотя точечный выпуск 1970 и Martin Odersky PR 1958.


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

Заблокировать scala.Enumeration, добавить @enum аннотацию

Синтаксис

@enum
 class Toggle {
  ON
  OFF
 }

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

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

Отменяет непобедимое бедствие, которое scala.Enumeration.

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

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

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

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

  • 0
    PR выше закрылся (без радости). Сейчас 2017 год и похоже, что Дотти наконец-то получит конструкцию enum. Вот проблема и пиар Мартина . Слияние, слияние, слияние!
8

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

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

5

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

4

Я видел различные версии создания класса 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)
}

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

  • 0
    Почему два отдельных неприменимых метода?
  • 0
    @jho Я пытался проработать ваше решение как есть, но оно не скомпилируется. Во втором фрагменте кода есть ссылка на сайт в "type V = Site". Я не уверен, к чему это относится, чтобы устранить ошибку компиляции. Далее, почему вы предоставляете пустые скобки для «абстрактного класса Currency»? Разве они не могут быть просто остановлены? Наконец, почему вы используете переменную "var values = ..."? Не означает ли это, что клиенты могут в любой момент в любом месте кода назначить новый список значениям? Разве не было бы гораздо предпочтительнее сделать его val, чем var?
2

Я предпочитаю 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 }
}
2

Я возвращался туда и обратно по этим двум параметрам в последние несколько раз, когда я нуждался в них. До недавнего времени мое предпочтение было связано с опцией закрытого объекта trait/case.

1) Scala Декларация перечисления

object OutboundMarketMakerEntryPointType extends Enumeration {
  type OutboundMarketMakerEntryPointType = Value

  val Alpha, Beta = Value
}

2) Запечатанные объекты + Объекты-объекты

sealed trait OutboundMarketMakerEntryPointType

case object AlphaEntryPoint extends OutboundMarketMakerEntryPointType

case object BetaEntryPoint extends OutboundMarketMakerEntryPointType

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

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

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

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

Объекты объекта/Запечатанные признаки

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

Против: -Инансируя из постоянного хранилища - вам часто приходится использовать сопоставление шаблонов здесь или определить свой собственный список всех возможных "значений enum"

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

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, когда значения предназначены для доступа из репозитория и объектов объекта/запечатанных признаков в противном случае.

  • 0
    Я вижу, насколько желателен второй шаблон кода (избавление от двух вспомогательных методов из первого шаблона кода). Тем не менее, я нашел способ, которым вы не обязаны выбирать между этими двумя шаблонами. Я покрываю весь домен в ответе, который я разместил в этой теме: stackoverflow.com/a/25923651/501113
0

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

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() = ...
}
0

Для тех, кто все еще смотрит, как получить ответ 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
}

Ещё вопросы

Сообщество Overcoder
Наверх
Меню