Как моделировать типы enum-типов?

320

Scala не имеет безопасных типов enum, таких как Java. Учитывая набор связанных констант, какой лучший способ в Scala представить эти константы?

  • 2
    Почему бы просто не использовать java enum? Это одна из немногих вещей, которые я до сих пор предпочитаю использовать обычную Java.
  • 1
    Я написал небольшой обзор о перечислении scala и его альтернативах, вы можете найти его полезным: pedrorijo.com/blog/scala-enums/
Теги:
enums

9 ответов

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

http://www.scala-lang.org/docu/files/api/scala/Enumeration.html

Пример использования

  object Main extends App {

    object WeekDay extends Enumeration {
      type WeekDay = Value
      val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value
    }
    import WeekDay._

    def isWorkingDay(d: WeekDay) = ! (d == Sat || d == Sun)

    WeekDay.values filter isWorkingDay foreach println
  }
  • 2
    Серьезно, приложение не должно использоваться. Это НЕ было исправлено; был представлен новый класс App, в котором нет проблем, упомянутых Шильдмейером. Так что "object foo extends App {...}" И у вас есть немедленный доступ к аргументам командной строки через переменную args.
  • 0
    scala.Enumeration (то, что вы используете в приведенном выше примере кода «Object WeekDay») не предлагает исчерпывающего сопоставления с образцом. Я исследовал все различные шаблоны перечисления, используемые в настоящее время в Scala, и дал и рассмотрю их в этом ответе StackOverflow (включая новый шаблон, который предлагает лучшее из обоих scala.Enumeration и шаблона «запечатанный trait + case object»: stackoverflow. ком / а / 25923651/501113
393

Я должен сказать, что пример, скопированный из документации Scala от skaffman выше, имеет ограниченную полезность на практике (вы могли бы также использовать case object s).

Чтобы получить что-то, самое близкое к Java Enum (то есть с разумными методами toString и valueOf - возможно, вы сохраняете значения перечисления в базе данных), вам нужно немного его изменить. Если вы использовали код skaffman:

WeekDay.valueOf("Sun") //returns None
WeekDay.Tue.toString   //returns Weekday(2)

Принимая во внимание следующее выражение:

object WeekDay extends Enumeration {
  type WeekDay = Value
  val Mon = Value("Mon")
  val Tue = Value("Tue") 
  ... etc
}

Вы получите более разумные результаты:

WeekDay.valueOf("Sun") //returns Some(Sun)
WeekDay.Tue.toString   //returns Tue
  • 7
    Btw. Метод valueOf теперь мертв :-(
  • 36
    Замена @macias valueOf - withName , который не возвращает Option и выдает NSE, если совпадений нет. Что за!
Показать ещё 6 комментариев
94

Существует много способов сделать.

1) Используйте символы. Тем не менее, это не даст вам никакой безопасности типов, кроме того, что вы не принимаете несимволы, где ожидается символ. Я просто упоминаю об этом здесь для полноты. Вот пример использования:

def update(what: Symbol, where: Int, newValue: Array[Int]): MatrixInt =
  what match {
    case 'row => replaceRow(where, newValue)
    case 'col | 'column => replaceCol(where, newValue)
    case _ => throw new IllegalArgumentException
  }

// At REPL:   
scala> val a = unitMatrixInt(3)
a: teste7.MatrixInt =
/ 1 0 0 \
| 0 1 0 |
\ 0 0 1 /

scala> a('row, 1) = a.row(0)
res41: teste7.MatrixInt =
/ 1 0 0 \
| 1 0 0 |
\ 0 0 1 /

scala> a('column, 2) = a.row(0)
res42: teste7.MatrixInt =
/ 1 0 1 \
| 0 1 0 |
\ 0 0 0 /

2) Используя класс Enumeration:

object Dimension extends Enumeration {
  type Dimension = Value
  val Row, Column = Value
}

или, если вам нужно сериализовать или отобразить его:

object Dimension extends Enumeration("Row", "Column") {
  type Dimension = Value
  val Row, Column = Value
}

Это можно использовать следующим образом:

def update(what: Dimension, where: Int, newValue: Array[Int]): MatrixInt =
  what match {
    case Row => replaceRow(where, newValue)
    case Column => replaceCol(where, newValue)
  }

// At REPL:
scala> a(Row, 2) = a.row(1)
<console>:13: error: not found: value Row
       a(Row, 2) = a.row(1)
         ^

scala> a(Dimension.Row, 2) = a.row(1)
res1: teste.MatrixInt =
/ 1 0 0 \
| 0 1 0 |
\ 0 1 0 /

scala> import Dimension._
import Dimension._

scala> a(Row, 2) = a.row(1)
res2: teste.MatrixInt =
/ 1 0 0 \
| 0 1 0 |
\ 0 1 0 /

К сожалению, он не гарантирует, что учитываются все совпадения. Если бы я забыл поставить Row или Column в матче, компилятор Scala не предупредил бы меня. Таким образом, это дает мне некоторую безопасность типов, но не настолько, насколько может быть достигнуто.

3) Объекты объекта:

sealed abstract class Dimension
case object Row extends Dimension
case object Column extends Dimension

Теперь, если я оставлю случай на match, компилятор предупредит меня:

MatrixInt.scala:70: warning: match is not exhaustive!
missing combination         Column

    what match {
    ^
one warning found

Он использовался почти так же, и даже не нужен import:

scala> val a = unitMatrixInt(3)
a: teste3.MatrixInt =
/ 1 0 0 \
| 0 1 0 |
\ 0 0 1 /

scala> a(Row,2) = a.row(0)
res15: teste3.MatrixInt =
/ 1 0 0 \
| 0 1 0 |
\ 1 0 0 /

Тогда вы можете подумать, зачем использовать Enumeration вместо объектов case. На самом деле, случайные объекты имеют много преимуществ, например, здесь. Класс Enumeration, однако, имеет множество методов Collection, таких как элементы (iterator on Scala 2.8), который возвращает Iterator, map, flatMap, filter и т.д.

Этот ответ по существу является выделенными частями от этой статьи в моем блоге.

52

Несколько менее подробный способ объявления именованных перечислений:

object WeekDay extends Enumeration("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat") {
  type WeekDay = Value
  val Sun, Mon, Tue, Wed, Thu, Fri, Sat = Value
}

WeekDay.valueOf("Wed") // returns Some(Wed)
WeekDay.Fri.toString   // returns Fri

Конечно, проблема заключается в том, что вам нужно будет сохранить порядок имен и валов в синхронизации, что проще сделать, если имя и val объявлены в одной строке.

  • 10
    На первый взгляд это выглядит чище, но его недостатком является то, что сопровождающему приходится поддерживать синхронизацию одера обоих списков. Для примера дней недели это не кажется вероятным. Но в целом, новое значение может быть вставлено, или одно удалено, и два списка могут быть не синхронизированы, и в этом случае могут появиться незначительные ошибки.
  • 1
    Согласно предыдущему комментарию, риск состоит в том, что два разных списка могут молча не синхронизироваться. Хотя это не проблема для вашего текущего небольшого примера, если есть еще много участников (например, от десятков до сотен), шансы двух списков, которые молча не синхронизируются, значительно выше. Также scala.Enumeration не может извлечь выгоду из исчерпывающих предупреждений / ошибок соответствия шаблонов во время компиляции Scala. Я создал ответ StackOverflow, который содержит решение, выполняющее проверку во время выполнения, чтобы гарантировать синхронизацию двух списков: stackoverflow.com/a/25923651/501113
17

Вместо перечисления можно использовать закрытый абстрактный класс, например:

sealed abstract class Constraint(val name: String, val verifier: Int => Boolean)

case object NotTooBig extends Constraint("NotTooBig", (_ < 1000))
case object NonZero extends Constraint("NonZero", (_ != 0))
case class NotEquals(x: Int) extends Constraint("NotEquals " + x, (_ != x))

object Main {

  def eval(ctrs: Seq[Constraint])(x: Int): Boolean =
    (true /: ctrs){ case (accum, ctr) => accum && ctr.verifier(x) }

  def main(args: Array[String]) {
    val ctrs = NotTooBig :: NotEquals(5) :: Nil
    val evaluate = eval(ctrs) _

    println(evaluate(3000))
    println(evaluate(3))
    println(evaluate(5))
  }

}
  • 0
    Запечатанная черта с предметами дела также возможна.
  • 2
    У шаблона «Запечатанные черты + объекты дела» есть проблемы, которые я подробно описал в ответе StackOverflow. Тем не менее, я выяснил, как решить все проблемы, связанные с этим шаблоном, который также рассматривается в теме: stackoverflow.com/a/25923651/501113
7

только что открыл enumeratum. это довольно удивительно и в равной степени удивительно, что он не более известен!

2

После обширного исследования всех вариантов "перечислений" в Scala я опубликовал гораздо более полный обзор этого домена в другом потоке https://stackoverflow.com/questions/1898932/case-objects-vs-enumerations-in-scala. Он включает в себя решение шаблона "запечатанный образец + случайный объект", где я решил проблему упорядочения инициализации класса/объекта JVM.

1

Dotty (Scala 3) будет иметь встроенные перечисления. Проверьте здесь и здесь.

0

В Scala очень удобно с https://github.com/lloydmeta/enumeratum

Проект действительно хорош с примерами и документацией

Просто этот пример из их документации должен вас заинтересовать

import enumeratum._

sealed trait Greeting extends EnumEntry

object Greeting extends Enum[Greeting] {

  /*
   'findValues' is a protected method that invokes a macro to find all 'Greeting' object declarations inside an 'Enum'

   You use it to implement the 'val values' member
  */
  val values = findValues

  case object Hello   extends Greeting
  case object GoodBye extends Greeting
  case object Hi      extends Greeting
  case object Bye     extends Greeting

}

// Object Greeting has a 'withName(name: String)' method
Greeting.withName("Hello")
// => res0: Greeting = Hello

Greeting.withName("Haro")
// => java.lang.IllegalArgumentException: Haro is not a member of Enum (Hello, GoodBye, Hi, Bye)

// A safer alternative would be to use 'withNameOption(name: String)' method which returns an Option[Greeting]
Greeting.withNameOption("Hello")
// => res1: Option[Greeting] = Some(Hello)

Greeting.withNameOption("Haro")
// => res2: Option[Greeting] = None

// It is also possible to use strings case insensitively
Greeting.withNameInsensitive("HeLLo")
// => res3: Greeting = Hello

Greeting.withNameInsensitiveOption("HeLLo")
// => res4: Option[Greeting] = Some(Hello)

// Uppercase-only strings may also be used
Greeting.withNameUppercaseOnly("HELLO")
// => res5: Greeting = Hello

Greeting.withNameUppercaseOnlyOption("HeLLo")
// => res6: Option[Greeting] = None

// Similarly, lowercase-only strings may also be used
Greeting.withNameLowercaseOnly("hello")
// => res7: Greeting = Hello

Greeting.withNameLowercaseOnlyOption("hello")
// => res8: Option[Greeting] = Some(Hello)

Ещё вопросы

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