Существуют ли рекомендации о том, когда использовать case-классы (или case-объекты) и расширять Enumeration в Scala?
Кажется, они предлагают некоторые из тех же преимуществ.
scala
enumeration
case-class
Алекс Миллер
источник
источник
enum
на базе Dotty (для середины 2020 года).Ответы:
Одно большое отличие состоит в том, что
Enumeration
они поддерживаются для создания экземпляров из некоторойname
строки. Например:Тогда вы можете сделать:
Это полезно при желании сохранить перечисления (например, в базе данных) или создать их из данных, находящихся в файлах. Тем не менее, я нахожу в целом, что перечисления немного неуклюжи в Scala и имеют ощущение неуклюжего дополнения, поэтому я теперь склоняюсь к использованию
case object
s.case object
более гибок, чем перечисление:Так что теперь у меня есть преимущество ...
Так как указал @ chaotic3quilibrium (с некоторыми исправлениями, облегчающими чтение):
Чтобы уточнить другие ответы здесь, основные недостатки
case object
S болееEnumeration
s:Невозможно перебрать все экземпляры перечисления . Это, конечно, так, но на практике я обнаружил, что это крайне редко требуется.
Не могу легко создать экземпляр из сохраняемой ценности . Это также верно, но, за исключением случая огромных перечислений (например, всех валют), это не представляет огромных накладных расходов.
источник
trade.ccy
образцу запечатанной черты.case
object
генерирует ли (~ 4x) больше места для кода, чемEnumeration
? Полезное различие, особенно дляscala.js
проектов, нуждающихся в небольшой площади.ОБНОВЛЕНИЕ: было создано новое решение на основе макросов, которое намного превосходит решение, которое я обрисовал в общих чертах ниже. Я настоятельно рекомендую использовать это новое решение на основе макросов . И, похоже, планы Дотти сделают этот стиль решения enum частью языка. Whoohoo!
Описание:
Есть три основных шаблона для попытки воспроизвести Java
Enum
в проекте Scala. Два из трех шаблонов; непосредственно с использованием JavaEnum
иscala.Enumeration
не способны обеспечить полное сопоставление с образцом в Scala. И третий; «запечатанный trait + case object», делает ..., но имеет сложности инициализации класса / объекта JVM, приводящие к непоследовательному порядковому индексу генерации.Я создал решение с двумя классами; Перечисление и Перечень оформлены , находятся в этом Гисте . Я не размещал код в этой теме, так как файл для Enumeration был довольно большим (+400 строк - содержит много комментариев, объясняющих контекст реализации).
Детали:
вопрос, который вы задаете, довольно общий; "... когда использовать
case
классыobjects
против расширения[scala.]Enumeration
". И оказывается, что есть МНОГИЕ возможные ответы, каждый ответ зависит от тонкостей конкретных требований проекта, которые у вас есть. Ответ может быть уменьшен до трех основных моделей.Для начала давайте удостоверимся, что мы работаем с той же базовой идеей, что и перечисление. Давайте определим перечисление в основном в терминах,
Enum
представленных в Java 5 (1.5) :Enum
, было бы неплохо иметь возможность явно использовать проверку исчерпывающего соответствия шаблонов Scala для перечисленияДалее давайте рассмотрим выложенные версии трех самых распространенных шаблонов решений:
A) На самом деле напрямую с использованием шаблона Java
Enum
(в смешанном проекте Scala / Java):Следующие элементы из определения перечисления недоступны:
Для моих текущих проектов у меня нет преимущества рисковать по пути смешанного проекта Scala / Java. И даже если бы я мог выбрать смешанный проект, пункт 7 крайне важен для того, чтобы позволить мне улавливать проблемы времени компиляции, если / когда я либо добавляю / удаляю членов перечисления, либо пишу какой-то новый код для работы с существующими членами перечисления.
Б) Используя шаблон «
sealed trait
+case objects
»:Следующие элементы из определения перечисления недоступны:
Можно утверждать, что он действительно соответствует пунктам 5 и 6 определения перечисления. Для 5 сложно утверждать, что он эффективен. Для 6 не так-то просто расширять для хранения дополнительных связанных данных одиночности.
C) Используя
scala.Enumeration
образец (вдохновленный этим ответом StackOverflow ):Следующие элементы из определения перечисления недоступны (оказывается, идентичны списку для непосредственного использования Java Enum):
Опять же, для моих текущих проектов, пункт 7 имеет решающее значение для того, чтобы позволить мне улавливать проблемы времени компиляции, если / когда я либо добавляю / удаляю членов перечисления, либо пишу какой-то новый код для работы с существующими членами перечисления.
Таким образом, учитывая приведенное выше определение перечисления, ни одно из трех приведенных выше решений не работает, поскольку они не обеспечивают всего, что изложено в приведенном выше определении перечисления:
Каждое из этих решений может быть в конечном счете переработано / расширено / реорганизовано, чтобы попытаться покрыть некоторые из недостающих требований каждого из них. Тем не менее, ни Java,
Enum
ниscala.Enumeration
решения не могут быть достаточно расширены, чтобы обеспечить пункт 7. И для моих собственных проектов это одна из наиболее убедительных ценностей использования закрытого типа в Scala. Я настоятельно предпочитаю, чтобы предупреждения / ошибки во время компиляции указывали на то, что в моем коде есть пробел / проблема, а не необходимость выявлять его из исключения / сбоя производственной среды выполнения.В связи с этим я приступил к работе с
case object
путем, чтобы посмотреть, смогу ли я найти решение, охватывающее все перечисленные выше определения. Первая задача состояла в том, чтобы протолкнуть ядро проблемы инициализации класса / объекта JVM (подробно описано в этом посте StackOverflow ). И я наконец смог найти решение.Поскольку мое решение - две черты; Enumeration и EnumerationDecorated , и поскольку эта
Enumeration
черта имеет длину более + 400 строк (множество комментариев, объясняющих контекст), я отказываюсь вставлять ее в эту ветку (что заставило бы ее заметно растягивать страницу). Для получения подробной информации, пожалуйста, перейдите прямо к Gist .Вот как выглядит решение, используя ту же идею данных, что и выше (полностью закомментированную версию, доступную здесь ) и реализованную в
EnumerationDecorated
.Это пример использования новой пары перечисленных черт, которые я создал (находится в этой 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 (удобные методы для краткости опущены):
Объекты ленивы; используя вместо этого vals, мы можем удалить список, но должны повторить имя:
Если вы не возражаете против обмана, вы можете предварительно загрузить значения перечисления, используя API отражений или что-то вроде Google Reflections. Не ленивые объекты case дают вам самый чистый синтаксис:
Хороший и чистый, со всеми преимуществами case-классов и перечислений Java. Лично я определяю значения перечисления вне объекта, чтобы лучше соответствовать идиоматическому коду Scala:
источник
Currency.values
, я получу только те значения, к которым ранее обращался. Есть ли способ обойти это?Преимущества использования case-классов перед перечислениями:
Преимущества использования перечислений вместо case-классов:
В общем, если вам нужен список простых констант по имени, используйте перечисления. В противном случае, если вам нужно что-то более сложное или вам нужна дополнительная безопасность компилятора, сообщающего вам, указаны ли все совпадения, используйте case-классы.
источник
ОБНОВЛЕНИЕ: код ниже имеет ошибку, описанную здесь . Тестовая программа, представленная ниже, работает, но если бы вы использовали DayOfWeek.Mon (например) перед самим DayOfWeek, она потерпела бы неудачу, поскольку DayOfWeek не был инициализирован (использование внутреннего объекта не приводит к инициализации внешнего объекта). Вы по-прежнему можете использовать этот код, если вы делаете что-то подобное
val enums = Seq( DayOfWeek )
в своем основном классе, вызывая инициализацию своих перечислений, или можете использовать модификации chaotic3quilibrium. С нетерпением ждем перечисления на основе макросов!Если вы хотите
тогда может представлять интерес следующее. Обратная связь приветствуется.
В этой реализации есть абстрактные базовые классы Enum и EnumVal, которые вы расширяете. Мы увидим эти классы через минуту, но сначала вот как вы определите перечисление:
Обратите внимание, что вы должны использовать каждое значение перечисления (вызвать его метод apply), чтобы воплотить его в жизнь. [Я бы хотел, чтобы внутренние объекты не были ленивыми, если я специально не попросил их об этом. Думаю.]
Конечно, мы можем добавить методы / данные в DayOfWeek, Val или отдельные объекты case, если мы того пожелаем.
А вот как бы вы использовали такое перечисление:
Вот что вы получите, когда его скомпилируете:
Вы можете заменить «день совпадения» на «(день: @unchecked) совпадение», если вы не хотите таких предупреждений, или просто включить в конец универсальный случай.
Когда вы запускаете вышеуказанную программу, вы получаете такой вывод:
Обратите внимание, что поскольку List и Maps являются неизменяемыми, вы можете легко удалять элементы для создания подмножеств, не нарушая сам перечисление.
Вот сам класс Enum (и EnumVal внутри него):
А вот более расширенное использование этого, которое контролирует идентификаторы и добавляет данные / методы к абстракции Val и к самому перечислению:
источник
var
] является пограничным смертным грехом в мире FP» - я не думаю, что это мнение общепризнанно.У меня есть хорошая простая библиотека, которая позволяет вам использовать запечатанные черты / классы в качестве значений перечисления без необходимости вести собственный список значений. Он опирается на простой макрос, который не зависит от ошибки
knownDirectSubclasses
.https://github.com/lloydmeta/enumeratum
источник
Обновление за март 2017: как прокомментировал Энтони Акчиоли ,
scala.Enumeration/enum
пиар был закрыт.Дотти (компилятор следующего поколения для Scala) возглавит дотти выпуск 1970 года и PR Мартина Одерского 1958 года .
Примечание: в настоящее время (август 2016 г., 6+ лет спустя) есть предложение удалить
scala.Enumeration
: PR 5352источник
Еще один недостаток классов case по сравнению с перечислениями, когда вам нужно будет выполнять итерации или фильтровать все экземпляры. Это встроенная возможность перечисления (и перечислений Java), в то время как case-классы автоматически не поддерживают такую возможность.
Другими словами: «нет простого способа получить список полного набора перечисляемых значений с помощью case-классов».
источник
Если вы серьезно относитесь к поддержанию взаимодействия с другими языками JVM (например, Java), тогда лучшим вариантом будет написать перечисления Java. Они работают прозрачно как из кода Scala, так и из кода Java, что больше, чем можно сказать, для
scala.Enumeration
объектов case. Давайте не будем иметь новую библиотеку перечислений для каждого нового хобби-проекта на GitHub, если этого можно избежать!источник
Я видел разные версии создания case-класса, имитирующего перечисление. Вот моя версия:
Что позволяет вам создавать классы case, которые выглядят следующим образом:
Возможно, кто-то может придумать лучший трюк, чем просто добавить класс каждого случая в список, как я. Это было все, что я мог придумать в то время.
источник
Я ходил туда-сюда по этим двум вариантам последние несколько раз, когда они мне были нужны. До недавнего времени я предпочитал опцию «Запечатанный объект / черта».
1) Счетная декларация Scala
2) Запечатанные Черты + Объекты Case
Хотя ни один из них на самом деле не соответствует всем перечисленным Java-перечислениям, ниже приведены плюсы и минусы:
Scala Enumeration
Плюсы: -Функции для создания экземпляров с опцией или непосредственного предположения о точности (проще при загрузке из постоянного хранилища) -Изменение всех возможных значений поддерживается
Минусы: - Предупреждение о компиляции для неполного поиска не поддерживается (делает сопоставление с шаблоном менее идеальным)
Объекты Case / Запечатанные черты
Плюсы: -Используя запечатанные черты, мы можем предварительно создать некоторые значения, в то время как другие могут быть введены во время создания -Полная поддержка для сопоставления с образцом (определены методы применения / отмены применения)
Минусы: - Создание экземпляров из постоянного хранилища - здесь часто приходится использовать сопоставление с образцом или определять собственный список всех возможных «значений перечисления».
Что в конечном итоге заставило меня изменить свое мнение, было что-то вроде следующего фрагмента:
Эти
.get
вызовы были отвратительны - с помощью перечисления вместо этого я могу просто вызвать метод withName на перечисление следующим образом :Поэтому я думаю, что в будущем я предпочитаю использовать Enumerations, если значения предназначены для доступа из репозитория, а объекты case / запечатанные признаки - иначе.
источник
Я предпочитаю
case objects
(это вопрос личных предпочтений). Чтобы справиться с проблемами, присущими этому подходу (анализ строки и итерация по всем элементам), я добавил несколько строк, которые не идеальны, но эффективны.Я вставляю вам код, ожидая, что он может быть полезен, а также чтобы другие могли его улучшить.
источник
Для тех, кто все еще ищет, как заставить работать ответ GatesDa : Вы можете просто сослаться на объект case после объявления его, чтобы создать его экземпляр:
источник
Я думаю , что самое большое преимущество иметь
case classes
болееenumerations
, что вы можете использовать шаблон класса типа аки однорангового полиморфизма . Не нужно сопоставлять перечисления как:вместо этого у вас будет что-то вроде:
источник