В Scala нет безопасных типов, enum
как в Java. Учитывая набор связанных констант, как лучше всего представить в Scala эти константы?
В Scala нет безопасных типов, enum
как в Java. Учитывая набор связанных констант, как лучше всего представить в Scala эти константы?
Ответы:
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
методами - возможно, вы сохраняете значения 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
valueOf
- это withName
, который не возвращает Option и выдает 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 вместо case-объектов. На самом деле, объекты case имеют многократные преимущества, например, здесь. Класс Enumeration, тем не менее, имеет много методов Collection, таких как элементы (итератор в Scala 2.8), который возвращает Iterator, map, flatMap, filter и т. Д.
Этот ответ по сути выбранных частей из этой статьи в моем блоге.
Symbol
экземпляры не могут иметь пробелов или специальных символов. Большинство людей, когда впервые встречаются с Symbol
классом, вероятно, думают так, но на самом деле это неправильно. Symbol("foo !% bar -* baz")
компилируется и работает отлично. Другими словами, вы можете прекрасно создавать Symbol
экземпляры, обертывающие любую строку (вы просто не можете сделать это с помощью синтаксического сахара "одиночная кома"). Единственное, что Symbol
гарантирует, - это уникальность любого данного символа, что делает его намного быстрее для сравнения и сопоставления.
String
, например, аргумент Symbol
параметра.
String
другим классом, который в основном является оберткой вокруг строки и может быть свободно преобразован в обоих направлениях (как в случае Symbol
). Я думаю, это то, что вы имели в виду, когда говорили «Это не даст вам никакой безопасности типов», просто это было не очень ясно, поскольку OP явно попросил решения по безопасности типов. Я не был уверен, что на момент написания статьи вы знали, что он не только не является безопасным типом, потому что они вообще не являются перечислениями, но также 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 . Он включает в себя решение шаблона «запечатанный trait + case object», где я решил проблему упорядочения инициализации класса / объекта 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)