Существуют ли рекомендации о том, когда использовать case-классы (или case-объекты) и расширять Enumeration в Scala?
Кажется, они предлагают некоторые из тех же преимуществ.
enum
на базе Dotty (для середины 2020 года).
Существуют ли рекомендации о том, когда использовать case-классы (или case-объекты) и расширять Enumeration в Scala?
Кажется, они предлагают некоторые из тех же преимуществ.
enum
на базе Dotty (для середины 2020 года).
Ответы:
Одно большое отличие состоит в том, что Enumeration
они поддерживаются для создания экземпляров из некоторой name
строки. Например:
object Currency extends Enumeration {
val GBP = Value("GBP")
val EUR = Value("EUR") //etc.
}
Тогда вы можете сделать:
val ccy = Currency.withName("EUR")
Это полезно при желании сохранить перечисления (например, в базе данных) или создать их из данных, находящихся в файлах. Тем не менее, я нахожу в целом, что перечисления немного неуклюжи в Scala и имеют ощущение неуклюжего дополнения, поэтому я теперь склоняюсь к использованию case object
s. 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
S болееEnumeration
s:
Невозможно перебрать все экземпляры перечисления . Это, конечно, так, но на практике я обнаружил, что это крайне редко требуется.
Не могу легко создать экземпляр из сохраняемой ценности . Это также верно, но, за исключением случая огромных перечислений (например, всех валют), это не представляет огромных накладных расходов.
trade.ccy
образцу запечатанной черты.
case
object
генерирует ли (~ 4x) больше места для кода, чем Enumeration
? Полезное различие, особенно для scala.js
проектов, нуждающихся в небольшой площади.
ОБНОВЛЕНИЕ: было создано новое решение на основе макросов, которое намного превосходит решение, которое я обрисовал в общих чертах ниже. Я настоятельно рекомендую использовать это новое решение на основе макросов . И, похоже, планы Дотти сделают этот стиль решения 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) :
Enum
, было бы неплохо иметь возможность явно использовать проверку исчерпывающего соответствия шаблонов Scala для перечисления Далее давайте рассмотрим выложенные версии трех самых распространенных шаблонов решений:
A) На самом деле напрямую с использованием шаблона JavaEnum
(в смешанном проекте 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;
}
}
Следующие элементы из определения перечисления недоступны:
Для моих текущих проектов у меня нет преимущества рисковать по пути смешанного проекта 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}
}
Следующие элементы из определения перечисления недоступны:
Можно утверждать, что он действительно соответствует пунктам 5 и 6 определения перечисления. Для 5 сложно утверждать, что он эффективен. Для 6 не так-то просто расширять для хранения дополнительных связанных данных одиночности.
C) Используя scala.Enumeration
образец (вдохновленный этим ответом StackOverflow ):
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):
Опять же, для моих текущих проектов, пункт 7 имеет решающее значение для того, чтобы позволить мне улавливать проблемы времени компиляции, если / когда я либо добавляю / удаляю членов перечисления, либо пишу какой-то новый код для работы с существующими членами перечисления.
Таким образом, учитывая приведенное выше определение перечисления, ни одно из трех приведенных выше решений не работает, поскольку они не обеспечивают всего, что изложено в приведенном выше определении перечисления:
Каждое из этих решений может быть в конечном счете переработано / расширено / реорганизовано, чтобы попытаться покрыть некоторые из недостающих требований каждого из них. Тем не менее, ни Java, Enum
ни scala.Enumeration
решения не могут быть достаточно расширены, чтобы обеспечить пункт 7. И для моих собственных проектов это одна из наиболее убедительных ценностей использования закрытого типа в Scala. Я настоятельно предпочитаю, чтобы предупреждения / ошибки во время компиляции указывали на то, что в моем коде есть пробел / проблема, а не необходимость выявлять его из исключения / сбоя производственной среды выполнения.
В связи с этим я приступил к работе с case object
путем, чтобы посмотреть, смогу ли я найти решение, охватывающее все перечисленные выше определения. Первая задача состояла в том, чтобы протолкнуть ядро проблемы инициализации класса / объекта JVM (подробно описано в этом посте StackOverflow ). И я наконец смог найти решение.
Поскольку мое решение - две черты; Enumeration и EnumerationDecorated , и поскольку эта Enumeration
черта имеет длину более + 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
}
Это пример использования новой пары перечисленных черт, которые я создал (находится в этой Gist ), для реализации всех возможностей, желательных и описанных в определении перечисления.
Выражается одно беспокойство: имена членов перечисления должны повторяться ( decorationOrderedSet
в приведенном выше примере). Хотя я свел его к минимуму до одного повтора, я не мог понять, как сделать это еще меньше, из-за двух проблем:
getClass.getDeclaredClasses
имеет неопределенный порядок (и вряд ли он будет в том же порядке, что и case object
объявления в исходном коде)Учитывая эти две проблемы, мне пришлось отказаться от попыток сгенерировать неявное упорядочение, и мне пришлось явно требовать, чтобы клиент определил и объявил его с каким-то понятием упорядоченного набора. Поскольку коллекции Scala не имеют реализации с упорядоченным набором вставок, лучшее, что я мог сделать, - это использовать List
и затем проверить во время выполнения, что это действительно набор. Это не то, как я бы предпочел добиться этого.
А с учетом конструкции требуется этот второй список / набор упорядоченность val
, учитывая ChessPiecesEnhancedDecorated
приведенный выше пример, можно было добавить , case object PAWN2 extends Member
а затем забудьте добавить Decoration(PAWN2,'P2', 2)
к decorationOrderedSet
. Итак, есть проверка во время выполнения, чтобы убедиться, что список не только набор, но и содержит ВСЕ объекты case, которые расширяют sealed trait Member
. Это была особая форма отражения / макро-ада, через которую нужно проработать.
Пожалуйста, оставляйте комментарии и / или отзывы о Gist .
org.scalaolio.util.Enumeration
и org.scalaolio.util.EnumerationDecorated
: scalaolio.org
Объекты 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
Currency.values
, я получу только те значения, к которым ранее обращался. Есть ли способ обойти это?
Преимущества использования case-классов перед перечислениями:
Преимущества использования перечислений вместо case-классов:
В общем, если вам нужен список простых констант по имени, используйте перечисления. В противном случае, если вам нужно что-то более сложное или вам нужна дополнительная безопасность компилятора, сообщающего вам, указаны ли все совпадения, используйте case-классы.
ОБНОВЛЕНИЕ: код ниже имеет ошибку, описанную здесь . Тестовая программа, представленная ниже, работает, но если бы вы использовали DayOfWeek.Mon (например) перед самим DayOfWeek, она потерпела бы неудачу, поскольку DayOfWeek не был инициализирован (использование внутреннего объекта не приводит к инициализации внешнего объекта). Вы по-прежнему можете использовать этот код, если вы делаете что-то подобное val enums = Seq( DayOfWeek )
в своем основном классе, вызывая инициализацию своих перечислений, или можете использовать модификации chaotic3quilibrium. С нетерпением ждем перечисления на основе макросов!
Если вы хотите
тогда может представлять интерес следующее. Обратная связь приветствуется.
В этой реализации есть абстрактные базовые классы 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)
}
var
] является пограничным смертным грехом в мире FP» - я не думаю, что это мнение общепризнанно.
У меня есть хорошая простая библиотека, которая позволяет вам использовать запечатанные черты / классы в качестве значений перечисления без необходимости вести собственный список значений. Он опирается на простой макрос, который не зависит от ошибкиknownDirectSubclasses
.
Обновление за март 2017: как прокомментировал Энтони Акчиоли , scala.Enumeration/enum
пиар был закрыт.
Дотти (компилятор следующего поколения для 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 по сравнению с перечислениями, когда вам нужно будет выполнять итерации или фильтровать все экземпляры. Это встроенная возможность перечисления (и перечислений Java), в то время как case-классы автоматически не поддерживают такую возможность.
Другими словами: «нет простого способа получить список полного набора перечисляемых значений с помощью case-классов».
Если вы серьезно относитесь к поддержанию взаимодействия с другими языками JVM (например, Java), тогда лучшим вариантом будет написать перечисления Java. Они работают прозрачно как из кода Scala, так и из кода Java, что больше, чем можно сказать, для scala.Enumeration
объектов case. Давайте не будем иметь новую библиотеку перечислений для каждого нового хобби-проекта на 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)
}
Возможно, кто-то может придумать лучший трюк, чем просто добавить класс каждого случая в список, как я. Это было все, что я мог придумать в то время.
Я ходил туда-сюда по этим двум вариантам последние несколько раз, когда они мне были нужны. До недавнего времени я предпочитал опцию «Запечатанный объект / черта».
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 / запечатанные признаки - иначе.
Я предпочитаю 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 }
}
Для тех, кто все еще ищет, как заставить работать ответ 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
, что вы можете использовать шаблон класса типа аки однорангового полиморфизма . Не нужно сопоставлять перечисления как:
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() = ...
}