Как моделировать типы перечислимого типа?
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)