У Scala немає безпечних типів enum
, як у Java. Враховуючи набір пов'язаних констант, який би кращий спосіб у Скалі представити ці константи?
У Scala немає безпечних типів enum
, як у Java. Враховуючи набір пов'язаних констант, який би кращий спосіб у Скалі представити ці константи?
Відповіді:
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 по skaffman вище має обмежену корисність на практиці (ви можете також використовувати case object
s).
Для того, щоб отримати щось, що найбільше нагадує Java Enum
(тобто з розумними toString
та valueOf
методами - можливо, ви зберігаєте значення перерахунку до бази даних), вам потрібно трохи змінити це. Якщо ви використовували код скафмана :
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
valueOf
є withName
, яка не повертає Варіант, і кидає NSE, якщо не відповідає. Що за!
Map[Weekday.Weekday, Long]
і додаю Mon
до нього значення, компілятор видає помилку помилкового типу. Очікуваний будній день. Знайдений вихідний день? Чому це відбувається?
Існує багато способів зробити це.
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
Тепер, якщо я залишу справу на a 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, однак, має багато методів колекції, таких як елементи (ітератор на Scala 2.8), який повертає ітератор, карту, flatMap, фільтр тощо.
Ця відповідь по суті є вибраною частиною цієї статті в моєму блозі.
Symbol
екземплярах не може бути пробілів чи спеціальних символів. Більшість людей при першій зустрічі з Symbol
класом, ймовірно, так думають, але насправді неправильно. Symbol("foo !% bar -* baz")
компілює і працює ідеально. Іншими словами, ви можете чудово створити Symbol
екземпляри, що обгортають будь-яку струну (просто не можете це зробити із синтаксичним цукром "єдиної коми"). Єдине, що Symbol
гарантує - це унікальність будь-якого даного символу, що робить його незначно швидшим порівняння та порівняння.
String
, наприклад, аргумент Symbol
параметру.
String
на інший клас, який в основному є обгорткою навколо рядка і може бути вільно перетворений в обох напрямках (як це має місце Symbol
). Я думаю, що це ви мали на увазі, кажучи "Це не дасть вам безпеки будь-якого типу", це було не зовсім зрозуміло, враховуючи, що ОП явно запитувала безпечні рішення для типу. Я не був впевнений, що під час написання ви знали, що це не тільки безпечний тип, оскільки вони зовсім не перераховані, але також Symbol
навіть не гарантують, що переданий аргумент не матиме спеціальних знаків.
'foo
позначення, яка не дозволяє. рядки без ідентифікатора). Це ця помилка, яку я хотів розвіяти для будь-якого майбутнього читача.
Трохи менш багатослівний спосіб оголошення названих перерахувань:
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, я опублікував значно більш повний огляд цього домену в іншій темі StackOverflow . Він включає рішення схеми "запечатаний ознака + об'єкт випадку", де я вирішив завдання впорядкування класу / об'єкта ініціалізації JVM.
У 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)