Как моделировать типы enum-типов? - PullRequest
310 голосов
/ 24 августа 2009

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

Ответы [ 9 ]

376 голосов
/ 24 августа 2009

Я должен сказать, что пример , скопированный из документации 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
186 голосов
/ 24 августа 2009

http://www.scala -lang.org / доку / файлы / 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
  }
98 голосов
/ 24 августа 2009

Есть много способов сделать.

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 /

К сожалению, это не гарантирует, что все совпадения учтены. Если бы я забыл поставить строку или столбец в совпадении, компилятор 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-объектов. На самом деле, объекты case имеют многократные преимущества, например, здесь. Класс Enumeration, тем не менее, имеет много методов Collection, таких как элементы (итератор в Scala 2.8), который возвращает Iterator, map, flatMap, filter и т. Д.

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

52 голосов
/ 24 августа 2009

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

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

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

17 голосов
/ 18 мая 2012

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

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

}
7 голосов
/ 25 мая 2016

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

2 голосов
/ 19 сентября 2014

После тщательного изучения всех вариантов «перечислений» в Scala я опубликовал гораздо более полный обзор этого домена в другой теме StackOverflow .Он включает в себя решение шаблона «запечатанный trait + case object», в котором я решил проблему упорядочения инициализации класса / объекта JVM.

1 голос
/ 19 декабря 2018

В 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)
1 голос
/ 02 июля 2018

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...