Как моделировать типы enum-типов?
У Scala нет безопасного типа enum
Это как Java. Учитывая набор связанных констант, как лучше всего представить в Scala эти константы?
10 ответов
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
}
Я должен сказать, что пример, скопированный из документации по Scala скаффманом выше, на практике имеет ограниченную полезность (вы также можете использовать case object
с).
Для того, чтобы получить что-то наиболее похожее на Java Enum
(то есть с разумным toString
а также valueOf
методы - возможно, вы сохраняете значения enum в базе данных) вам нужно немного его изменить. Если вы использовали код Скаффмана:
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
Есть много способов сделать.
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 и т. Д.
Этот ответ, по сути, выбранные части из этой статьи в моем блоге.
Чуть менее подробный способ объявления именованных перечислений:
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
Конечно, проблема здесь в том, что вам нужно будет синхронизировать порядок имен и значений, что проще сделать, если имена и значения объявлены в одной строке.
Вы можете использовать запечатанный абстрактный класс вместо перечисления, например:
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))
}
}
Только что обнаружил перечисление. это довольно удивительно и столь же удивительно, это не более известно!
После тщательного изучения всех вариантов "перечислений" в Scala я опубликовал гораздо более полный обзор этого домена в другой ветке Stackru. Он включает в себя решение шаблона "запечатанный trait + case object", в котором я решил проблему упорядочения инициализации класса / объекта JVM.
Начиная со Scala 3, теперь есть ключевое слово enum , которое может представлять набор констант (и другие варианты использования).
enum Color:
case Red, Green, Blue
scala> val red = Color.Red
val red: Color = Red
scala> red.ordinal
val res0: Int = 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)