Scala nie ma bezpiecznych typów, enum
takich jak Java. Biorąc pod uwagę zestaw powiązanych stałych, jaki byłby najlepszy sposób w Scali do przedstawienia tych stałych?
Scala nie ma bezpiecznych typów, enum
takich jak Java. Biorąc pod uwagę zestaw powiązanych stałych, jaki byłby najlepszy sposób w Scali do przedstawienia tych stałych?
Odpowiedzi:
http://www.scala-lang.org/docu/files/api/scala/Enumeration.html
Przykładowe użycie
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
}
Muszę powiedzieć, że przykład skopiowany z powyższej dokumentacji Scali przez skaffmana ma ograniczoną użyteczność w praktyce (równie dobrze możesz użyć case object
s).
Aby uzyskać coś najbardziej przypominającego Javę Enum
(tj. Z rozsądnymi toString
i valueOf
metodami - być może utrwalasz wartości wyliczeniowe w bazie danych), musisz ją nieco zmodyfikować. Jeśli używałeś kodu skaffmana :
WeekDay.valueOf("Sun") //returns None
WeekDay.Tue.toString //returns Weekday(2)
Podczas korzystania z następującej deklaracji:
object WeekDay extends Enumeration {
type WeekDay = Value
val Mon = Value("Mon")
val Tue = Value("Tue")
... etc
}
Otrzymujesz bardziej sensowne wyniki:
WeekDay.valueOf("Sun") //returns Some(Sun)
WeekDay.Tue.toString //returns Tue
valueOf
jest takie withName
, które nie zwraca Opcji i rzuca NSE, jeśli nie ma dopasowania. Co!
Map[Weekday.Weekday, Long]
i dodać wartość powiedzieć Mon
do niego, kompilator zgłasza błąd typu nieprawidłowego. Oczekiwany dzień tygodnia. Dzień tygodnia znalazł wartość? Dlaczego to się dzieje?
Istnieje wiele sposobów działania.
1) Użyj symboli. Nie zapewni ci jednak żadnego bezpieczeństwa, poza tym, że nie akceptujesz nie-symboli, w których spodziewany jest symbol. Wspominam o tym tutaj tylko dla kompletności. Oto przykład użycia:
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) Korzystanie z klasy Enumeration
:
object Dimension extends Enumeration {
type Dimension = Value
val Row, Column = Value
}
lub, jeśli chcesz go serializować lub wyświetlić:
object Dimension extends Enumeration("Row", "Column") {
type Dimension = Value
val Row, Column = Value
}
Można to wykorzystać w następujący sposób:
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 /
Niestety nie gwarantuje, że wszystkie mecze zostaną uwzględnione. Gdybym zapomniał umieścić Row lub Column w meczu, kompilator Scala nie ostrzegłby mnie. Daje mi to pewne bezpieczeństwo, ale nie tyle, ile można uzyskać.
3) Obiekty sprawy:
sealed abstract class Dimension
case object Row extends Dimension
case object Column extends Dimension
Teraz, jeśli pominę sprawę match
, kompilator ostrzeże mnie:
MatrixInt.scala:70: warning: match is not exhaustive!
missing combination Column
what match {
^
one warning found
Jest używany prawie w ten sam sposób i nawet nie potrzebuje 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 /
Zastanawiasz się zatem, dlaczego kiedykolwiek używać Wyliczenia zamiast obiektów sprawy. W rzeczywistości obiekty przypadków mają wiele zalet, na przykład tutaj. Klasa Enumeration ma jednak wiele metod Collection, takich jak elementy (iterator na Scali 2.8), które zwracają Iterator, mapę, flatMap, filtr itp.
Ta odpowiedź jest zasadniczo wybranymi częściami tego artykułu na moim blogu.
Symbol
instancje nie mogą zawierać spacji ani znaków specjalnych. Większość osób, które spotykają się z Symbol
klasą po raz pierwszy, prawdopodobnie tak uważa, ale w rzeczywistości jest to nieprawidłowe. Symbol("foo !% bar -* baz")
kompiluje się i działa idealnie dobrze. Innymi słowy, możesz idealnie tworzyć Symbol
instancje owijające dowolny ciąg znaków (po prostu nie możesz tego zrobić z cukrem składniowym „pojedyncza koma”). Jedyną rzeczą, Symbol
która gwarantuje, jest wyjątkowość każdego danego symbolu, dzięki czemu porównanie i dopasowanie jest nieznacznie szybsze.
String
na przykład argumentu Symbol
parametru.
String
inną klasą, która jest w zasadzie owijką wokół łańcucha i może być swobodnie konwertowana w obu kierunkach (jak ma to miejsce Symbol
). Myślę, że właśnie to miałeś na myśli mówiąc „Nie da to żadnego bezpieczeństwa”, po prostu nie było to bardzo jasne, biorąc pod uwagę, że OP wyraźnie poprosił o bezpieczne rozwiązania typu. Nie byłem pewien, czy w momencie pisania wiedziałeś, że nie tylko nie jest to bezpieczne, ponieważ w ogóle nie są to wyliczenia, ale także Symbol
nie gwarantuje, że przekazany argument nie będzie zawierał specjalnych znaków.
'foo
notacji który dokłada wyklucza ciągi nie będące identyfikatorami). To nieporozumienie, które chciałem rozwiać dla każdego przyszłego czytelnika.
Nieco bardziej szczegółowy sposób deklarowania nazwanych wyliczeń:
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
Oczywiście problem polega na tym, że trzeba będzie zsynchronizować kolejność nazw i valsów, co jest łatwiejsze, jeśli nazwa i val są zadeklarowane w tym samym wierszu.
Możesz użyć zapieczętowanej klasy abstrakcyjnej zamiast wyliczenia, na przykład:
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))
}
}
właśnie odkryłem enumeratum . jest niesamowity i równie niesamowity, nie jest bardziej znany!
Po dogłębnych badaniach wszystkich opcji dotyczących „wyliczeń” w Scali, opublikowałem znacznie pełniejszy przegląd tej domeny w innym wątku StackOverflow . Zawiera rozwiązanie wzoru „uszczelniona cecha + obiekt sprawy”, w którym rozwiązałem problem z kolejnością inicjowania klasy / obiektu JVM.
W Scali jest bardzo wygodnie z https://github.com/lloydmeta/enumeratum
Projekt jest naprawdę dobry z przykładami i dokumentacją
Właśnie ten przykład z ich dokumentów powinien cię zainteresować
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)