Как моделировать типы перечислимого типа?

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

Ответ 1

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

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

Ответ 3

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

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 и т.д.

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

Ответ 4

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

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 объявлены в одной строке.

Ответ 5

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

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

}

Ответ 6

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

Ответ 7

После обширного исследования всех вариантов "перечислений" в Scala я опубликовал гораздо более полный обзор этого домена в другом потоке fooobar.com/questions/1454/.... Он включает в себя решение шаблона "запечатанный образец + случайный объект", где я решил проблему упорядочения инициализации класса/объекта JVM.

Ответ 8

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

Ответ 9

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