2009-08-24 19 views
299

Scala nie ma typu bezpiecznego enum s jak Java ma. Biorąc pod uwagę zestaw powiązanych stałych, jaki byłby najlepszy sposób reprezentowania tych stałych w Scali?Jak modelować typy enum bezpieczne dla typu?

+2

Dlaczego nie używać emulacji języka Java? Jest to jedna z niewielu rzeczy, które wolę używać zwykłego java. – Max

+1

Napisałem mały przegląd o wyliczaniu scala i alternatywach, może się okazać, że jest to przydatne: pedrorijo.com/blog/scala-enums/ – pedrorijo91

Odpowiedz

179

http://www.scala-lang.org/docu/files/api/scala/Enumeration.html

Przykład korzystania

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

Poważnie, aplikacja nie powinna być używana. NIE został naprawiony; wprowadzono nową klasę App, która nie ma problemów wspomnianych przez Schildmeijera. Podobnie jak "object foo rozszerza App {...}" I masz natychmiastowy dostęp do argumentów wiersza poleceń za pośrednictwem zmiennej args. – AmigoNico

+0

scala.Enumeration (którego używasz w powyższym przykładzie kodu "obiektu WeekDay") nie oferuje wyczerpującego dopasowania wzorców. Zbadałem wszystkie różne wzorce wyliczania, które są obecnie używane w Scali, oraz podałem i przejrzałem je w odpowiedzi StackOverflow (w tym nowy wzór, który oferuje najlepsze cechy zarówno scala.Enumeration, jak i wzorzec "sealed trait + case": http: //stackoverflow.com/a/25923651/501113 – chaotic3quilibrium

372

muszę powiedzieć, że przykład kopiowane z dokumentacją Scala przez skaffman powyżej ma ograniczoną przydatność w praktyce (równie dobrze można użyć case object s).

W celu uzyskania czegoś najbardziej przypominający Java Enum (tj z sensownych toString i valueOf metod - może jesteś utrzymujących wartości enum w bazie danych) trzeba zmodyfikować go trochę. Jeśli użył skaffman „s kod:

WeekDay.valueOf("Sun") //returns None 
WeekDay.Tue.toString //returns Weekday(2) 

Zważywszy, stosując następującą deklarację:

object WeekDay extends Enumeration { 
    type WeekDay = Value 
    val Mon = Value("Mon") 
    val Tue = Value("Tue") 
    ... etc 
} 

uzyskać wyniki bardziej sensowne:

WeekDay.valueOf("Sun") //returns Some(Sun) 
WeekDay.Tue.toString //returns Tue 
+6

Metoda btw. valueOf jest już martwa :-( – greenoldman

+35

@macias Zastąpienie 'valueOf' to' withName', który nie zwraca opcji, i wyrzuca NSE, jeśli istnieje nie może się równać – Bluu

+6

@Bluu Możesz dodać valueOf siebie: def valueOf (name: String) = WeekDay.values.find (_. toString == name) aby mieć opcję – centr

52

Nieco mniej gadatliwy drodze deklarowanie 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 należy zachować synchronizację nazw i oznaczeń, co jest łatwiejsze, gdy nazwy i wartości są zadeklarowane w tym samym wierszu.

+10

Wygląda to na pierwszy rzut oka czystsze, ale ma tę wadę, że wymaga od opiekuna utrzymania synchronizacji obu list. Dla przykładu dni tygodnia nie wydaje się prawdopodobne. Ogólnie można jednak dodać nową wartość lub jedną usunąć, a dwie listy mogą być niezsynchronizowane, w takim przypadku można wprowadzić subtelne błędy. –

+1

Według wcześniejszego komentarza istnieje ryzyko, że dwie różne listy mogą po cichu przestać zsynchronizować się.Chociaż nie jest to problemem dla twojego obecnego małego przykładu, jeśli jest o wiele więcej członków (jak w dziesiątkach do setek), szanse na dwie listy po cichu znikają, jest znacznie wyższa. Również scala.Enumeration nie może korzystać z wyczerpujących ostrzeżeń/błędów pasujących do wzorca w Scala. Utworzyłem odpowiedź StackOverflow, która zawiera rozwiązanie wykonujące kontrolę środowiska wykonawczego, aby zapewnić synchronizację dwóch list: http://stackoverflow.com/a/25923651/501113 – chaotic3quilibrium

96

Istnieje wiele sposobów robienia.

1) Użyj symboli. Nie zapewni to jednak żadnego bezpieczeństwa, poza tym, że nie akceptuje nie-symboli, gdy oczekiwany jest symbol. Wspominam o tym tylko tutaj 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 trzeba do serializacji lub wyświetlić go:

object Dimension extends Enumeration("Row", "Column") { 
    type Dimension = Value 
    val Row, Column = Value 
} 

ten może być stosowany tak :

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/

Unfortu nately nie zapewnia, że ​​wszystkie mecze są rozliczane. Jeśli zapomniałbym umieścić wiersz lub kolumnę w meczu, kompilator Scali nie ostrzegłby mnie. Tak więc daje mi pewne bezpieczeństwo, ale nie tak dużo, jak można uzyskać.

3) Case obiektów:

sealed abstract class Dimension 
case object Row extends Dimension 
case object Column extends Dimension 

Teraz, jeśli pominąć sprawę na match, kompilator ostrzega mnie:

MatrixInt.scala:70: warning: match is not exhaustive! 
missing combination   Column 

    what match { 
    ^
one warning found 

jest używany dość dużo w ten sam sposób, a nawet nie potrzebować 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/

można się zastanawiać, a następnie, dlaczego nigdy nas e Wyliczenie zamiast obiektów sprawy. W rzeczywistości obiekty typu case mają wiele zalet, na przykład tutaj. Jednak klasa Enumeration ma wiele metod kolekcjonowania, takich jak elementy (iterator w Scala 2.8), która zwraca Iterator, mapę, mapę płaską, filtr itd.

Ta odpowiedź jest w istocie wybraną częścią z this article w moim blogu .

16

Można użyć szczelną abstrakcyjna klasa 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)) 
    } 

} 
+0

Cecha zamknięta z obiektami przypadku jest również możliwa. – Ashalynd

+2

Wzorzec "sealed trait + case objects" zawiera problemy, które szczegółowo opiszę w odpowiedzi StackOverflow. Jednak wymyśliłem, jak rozwiązać wszystkie problemy związane z tym wzorem, który jest również objęty wątkiem: http://stackoverflow.com/a/25923651/501113 – chaotic3quilibrium

2

Po przeprowadzeniu szeroko zakrojonych badań nad wszystkimi opcjami wokół „wyliczenia” w Scala, Zamieściłem znacznie pełniejszy obraz tego domena na innym StackOverflow thread. Zawiera rozwiązanie wzoru "zapieczętowana cecha + obiekt sprawy", w którym rozwiązałem problem porządkowania klasy JVM/obiektu.