Obiekty Case a wyliczenia w Scali
Czy są jakieś wskazówki dotyczące najlepszych praktyk, kiedy używać klas case (lub obiektów case) zamiast rozszerzania wyliczeń w Scali?
Wydają się oferować niektóre z tych samych korzyści.
13 answers
Jedną dużą różnicą jest to, że Enumeration
są dostarczane z obsługą tworzenia instancji z jakiegoś name
ciągu. Na przykład:
object Currency extends Enumeration {
val GBP = Value("GBP")
val EUR = Value("EUR") //etc.
}
Wtedy możesz zrobić:
val ccy = Currency.withName("EUR")
Jest to przydatne, gdy chcemy utrzymywać wyliczenia (na przykład do bazy danych) lub tworzyć je z danych znajdujących się w plikach. Jednak ogólnie uważam, że wyliczenia są trochę niezgrabne w Scali i mają poczucie niezręcznego dodatku, więc teraz używam case object
s. a case object
jest bardziej elastyczny niż enum:
sealed trait Currency { def name: String }
case object EUR extends Currency { val name = "EUR" } //etc.
case class UnknownCurrency(name: String) extends Currency
Więc teraz mam przewagę...
trade.ccy match {
case EUR =>
case UnknownCurrency(code) =>
}
Jako @chaotic3quilibrium wskazał (z pewnymi poprawkami ułatwiającymi czytanie):
Jeśli chodzi o wzorzec" UnknownCurrency(code)", istnieją inne sposoby radzenia sobie z nie znajdowaniem ciągu kodu waluty niż" łamanie " zamkniętego zbioru typu
Currency
.UnknownCurrency
bycie typuCurrency
może teraz wkraść się do innych części API.Wskazane jest, aby wypchnąć tę sprawę na zewnątrz
Enumeration
i zrobić Klient ma do czynienia z typemOption[Currency]
, który wyraźnie wskazuje, że naprawdę istnieje problem dopasowania i "zachęca" użytkownika API do samodzielnego rozwiązania tego problemu.
Aby kontynuować inne odpowiedzi tutaj, główne wady case object
s nad Enumeration
S są:
nie można iterować wszystkich instancji "wyliczenia" . Z pewnością tak jest, ale w praktyce okazało się, że jest to niezwykle rzadkie.
Can ' t łatwo utworzyć instancję z trwałej wartości . Jest to również prawdą, ale z wyjątkiem ogromnych wyliczeń (na przykład wszystkich walut), nie stanowi to dużego obciążenia.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:38
Obiekty Case zwracają już swoją nazwę dla swoich metod toString, więc przekazywanie ich osobno jest niepotrzebne. Oto wersja podobna do jho (metody wygody pominięte dla zwięzłości):
trait Enum[A] {
trait Value { self: A => }
val values: List[A]
}
sealed trait Currency extends Currency.Value
object Currency extends Enum[Currency] {
case object EUR extends Currency
case object GBP extends Currency
val values = List(EUR, GBP)
}
Obiekty są leniwe; używając vals zamiast tego możemy upuścić listę, ale musimy powtórzyć nazwę:
trait Enum[A <: {def name: String}] {
trait Value { self: A =>
_values :+= this
}
private var _values = List.empty[A]
def values = _values
}
sealed abstract class Currency(val name: String) extends Currency.Value
object Currency extends Enum[Currency] {
val EUR = new Currency("EUR") {}
val GBP = new Currency("GBP") {}
}
Jeśli nie masz nic przeciwko oszukiwaniu, możesz wstępnie załadować wartości wyliczania za pomocą API reflection lub czegoś takiego jak Google Reflections. Nie-leniwe obiekty case dają najczystsza składnia:
trait Enum[A] {
trait Value { self: A =>
_values :+= this
}
private var _values = List.empty[A]
def values = _values
}
sealed trait Currency extends Currency.Value
object Currency extends Enum[Currency] {
case object EUR extends Currency
case object GBP extends Currency
}
Ładne i czyste, ze wszystkimi zaletami klas case i wyliczeń Javy. OsobiĹ "cie definiujÄ ™ wartoĹ" ci wyliczeĺ " poza obiektem, aby lepiej dopasowaÄ ‡ kod idiomatic Scala:
object Currency extends Enum[Currency]
sealed trait Currency extends Currency.Value
case object EUR extends Currency
case object GBP extends Currency
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-10 15:18:23
Aktualizacja: Stworzono nowe rozwiązanie oparte na makrach , które znacznie przewyższa rozwiązanie, które przedstawiam poniżej. Zdecydowanie polecam użycie tego nowego rozwiązania opartego na makrach . i wygląda na to, że plany Dotty uczynią ten styl rozwiązania enum częścią języka. Whoohoo!
Podsumowanie:
Istnieją trzy podstawowe wzorce do prób odtworzenia Javy Enum
w ramach projektu Scala. Dwa z trzech wzorów; bezpośrednio korzystanie z Javy Enum
i scala.Enumeration
nie jest w stanie włączyć pełnego dopasowania wzorców Scali. I trzeci; "sealed trait + case object", robi...ale ma komplikacje inicjalizacji klasy/obiektu JVM powodujące niespójne generowanie indeksu porządkowego.
Stworzyłem rozwiązanie z dwoma klasami; Enumeration i EnumerationDecorated , znajdujące się w tym Gist . Nie wrzuciłem kodu do tego wątku ponieważ plik do wyliczenia był dość duży (+400 linii-zawiera mnóstwo komentarzy wyjaśniających kontekst implementacji).
szczegóły:
Pytanie, które zadajesz jest dość ogólne;"...kiedy używać case
klasyobjects
vs Extended [scala.]Enumeration
". I okazuje się, że istnieje wiele możliwych odpowiedzi, każda odpowiedź w zależności od subtelności konkretnych wymagań projektu masz. Odpowiedź można zredukować do trzech podstawowych wzorów.
Na początek upewnijmy się, że pracujemy od to samo podstawowe pojęcie o tym, czym jest wyliczenie. Zdefiniujmy wyliczenie głównie w kategoriach Enum
dostarczone w wersji Java 5 (1.5):
- zawiera naturalnie uporządkowany zamknięty zbiór nazwanych członków
- istnieje stała liczba członków
- członkowie są naturalnie uporządkowani i wyraźnie indeksowani
- W przeciwieństwie do sortowania w oparciu o pewne kryteria kwerendy członka inate
- ]}
- każdy członek ma unikalną nazwę w total set of all members
- wszyscy członkowie mogą być łatwo iterowani na podstawie ich indeksów
- członek może być pobrany z jego (rozróżniającą wielkość liter) nazwą
- byłoby całkiem miło, gdyby członek mógł być również pobrany z jego nazwą bez rozróżniania wielkości liter
- członek może być pobrany z jego indeksem Użytkownicy mogą łatwo, przejrzyście i efektywnie korzystać z serializacji.]}
- członków można łatwo rozszerzyć na przechowuj dodatkowe powiązane dane singleton-Ness]} [10]), ale nie tylko, jeśli chodzi o Javę, byłoby miło móc wyraźnie wykorzystać sprawdzanie wyczerpującej zgodności wzorców Scali w celu wyliczenia [10]]}
Następnie przyjrzyjmy się trzem najczęściej umieszczanym wzorom rozwiązań:
A) faktycznie bezpośrednio przy użyciu Java Enum
pattern (w mieszanym projekcie Scala / Java):
public enum ChessPiece {
KING('K', 0)
, QUEEN('Q', 9)
, BISHOP('B', 3)
, KNIGHT('N', 3)
, ROOK('R', 5)
, PAWN('P', 1)
;
private char character;
private int pointValue;
private ChessPiece(char character, int pointValue) {
this.character = character;
this.pointValue = pointValue;
}
public int getCharacter() {
return character;
}
public int getPointValue() {
return pointValue;
}
}
Następujące pozycje z definicja wyliczenia nie jest dostępna:
- 3.1-byłoby całkiem miło, gdyby członek mógł być również pobrany z jego nazwą niewrażliwą na wielkość liter
- 7-myśląc poza wyliczeniem Java, byłoby miło móc jawnie użyć sprawdzania wyczerpalności mapowania wzorców Scala do wyliczenia
W przypadku moich obecnych projektów, nie mam korzyści z podejmowania ryzyka wokół mieszanej ścieżki projektu Scala / Java. I nawet gdybym mógł wybrać mieszany projekt, pozycja 7 jest krytyczna, ponieważ pozwala mi złapać problemy z czasem kompilacji, jeśli / kiedy dodaję/usuwam członków wyliczania, lub piszę jakiś nowy kod, aby poradzić sobie z istniejącymi członkami wyliczania.
B) używając "sealed trait
+ case objects
" wzór:
sealed trait ChessPiece {def character: Char; def pointValue: Int}
object ChessPiece {
case object KING extends ChessPiece {val character = 'K'; val pointValue = 0}
case object QUEEN extends ChessPiece {val character = 'Q'; val pointValue = 9}
case object BISHOP extends ChessPiece {val character = 'B'; val pointValue = 3}
case object KNIGHT extends ChessPiece {val character = 'N'; val pointValue = 3}
case object ROOK extends ChessPiece {val character = 'R'; val pointValue = 5}
case object PAWN extends ChessPiece {val character = 'P'; val pointValue = 1}
}
Następujące pozycje z definicji wyliczenia nie są dostępne:
- 1.2 - członkowie są naturalnie uporządkowani i wyraźnie indeksowani
- 2 - wszyscy członkowie mogą być łatwo iterowane na podstawie ich indeksów
- 3-element może być pobrany z jego nazwą (rozróżniającą wielkość liter)
- 3.1-byłoby całkiem miło, gdyby członek mógł być również pobrany z jego nazwą niewrażliwą na wielkość liter
- 4-członek może być pobrany z jego indeksem
Jest prawdopodobne, że naprawdę spełnia definicję wyliczenia punktów 5 i 6. Dla 5, to jest rozciągnięcie, aby twierdzić, że jest skuteczny. Dla 6, to nie jest naprawdę łatwe do przedłużenia, aby trzymać dodatkowe związane singleton-Ness data.
C) używając scala.Enumeration
pattern (inspired by this StackOverflow answer ):
object ChessPiece extends Enumeration {
val KING = ChessPieceVal('K', 0)
val QUEEN = ChessPieceVal('Q', 9)
val BISHOP = ChessPieceVal('B', 3)
val KNIGHT = ChessPieceVal('N', 3)
val ROOK = ChessPieceVal('R', 5)
val PAWN = ChessPieceVal('P', 1)
protected case class ChessPieceVal(character: Char, pointValue: Int) extends super.Val()
implicit def convert(value: Value) = value.asInstanceOf[ChessPieceVal]
}
W Javie Enumeracja nie jest dostępna (tak się składa, że jest identyczna z listą do bezpośredniego użycia Java Enum):
- 3.1-byłoby całkiem miło, gdyby członek mógł być również pobrany z jego nazwą niewrażliwą na wielkość liter
- 7-myślenie poza Enum Javy, byłoby miło móc w 1995 roku Scala została przekształcona w Scala Scala.]}
Ponownie dla moich obecnych projektów, pozycja 7 jest krytyczna, ponieważ pozwala mi złapać problemy z czasem kompilacji, jeśli / kiedy dodaję/usuwam członków wyliczania lub piszę nowy kod, aby poradzić sobie z istniejącymi członkami wyliczania.
Tak więc, biorąc pod uwagę powyższą definicję wyliczenia, żadne z powyższych trzech rozwiązań nie działa, ponieważ nie dostarczają one wszystkiego, co jest opisane w definicja wyliczenia powyżej:
- Java Enum bezpośrednio w mieszanym projekcie Scala / Java
- "sealed trait + Case objects"
- scala.Enumeracja
Każde z tych rozwiązań może być ostatecznie przerobione/rozszerzone/refakturowane, aby spróbować pokryć niektóre z brakujących wymagań każdego z nich. Jednak ani rozwiązania Java Enum
, ani scala.Enumeration
nie mogą być wystarczająco rozbudowane, aby zapewnić pozycję 7. A dla moich własnych projektów jest to jeden z bardziej fascynujących wartości użycia typu zamkniętego w Scali. Zdecydowanie wolę Ostrzeżenia/błędy w czasie kompilacji, aby wskazać, że mam lukę / problem w moim kodzie, zamiast konieczności zbierania go z wyjątku/błędu środowiska produkcyjnego.
W związku z tym rozpocząłem pracę ze ścieżką case object
, aby zobaczyć, czy mogę stworzyć rozwiązanie, które obejmowało wszystkie powyższe definicje wyliczeń. Pierwszym wyzwaniem było przejście przez rdzeń problemu inicjalizacji klasy/obiektu JVM (omówionego szczegółowo w ten post StackOverflow ). I w końcu udało mi się znaleźć rozwiązanie.
Ponieważ moim rozwiązaniem są dwie cechy; wyliczenie i wyliczenie , a ponieważ Enumeration
cecha ma ponad + 400 linii (wiele komentarzy wyjaśniających kontekst), rezygnuję z wklejania jej do tego wątku (co sprawiłoby, że rozciągnęłaby się w dół strony). Aby uzyskać szczegółowe informacje, przejdź bezpośrednio do Gist.
Oto, jak wygląda rozwiązanie podobnie jak przy użyciu tego samego pomysłu danych jak powyżej (wersja w pełni skomentowana dostępna tutaj) i zaimplementowana w EnumerationDecorated
.
import scala.reflect.runtime.universe.{TypeTag,typeTag}
import org.public_domain.scala.utils.EnumerationDecorated
object ChessPiecesEnhancedDecorated extends EnumerationDecorated {
case object KING extends Member
case object QUEEN extends Member
case object BISHOP extends Member
case object KNIGHT extends Member
case object ROOK extends Member
case object PAWN extends Member
val decorationOrderedSet: List[Decoration] =
List(
Decoration(KING, 'K', 0)
, Decoration(QUEEN, 'Q', 9)
, Decoration(BISHOP, 'B', 3)
, Decoration(KNIGHT, 'N', 3)
, Decoration(ROOK, 'R', 5)
, Decoration(PAWN, 'P', 1)
)
final case class Decoration private[ChessPiecesEnhancedDecorated] (member: Member, char: Char, pointValue: Int) extends DecorationBase {
val description: String = member.name.toLowerCase.capitalize
}
override def typeTagMember: TypeTag[_] = typeTag[Member]
sealed trait Member extends MemberDecorated
}
Jest to przykład użycia nowej pary cech wyliczania, które stworzyłem (znajdującej się w niniejszym Gist ), aby zaimplementować wszystkie możliwości pożądane i opisane w definicji wyliczania.
Jednym z powodów jest konieczność powtórzenia nazw członków wyliczenia (decorationOrderedSet
w powyższym przykładzie). Chociaż zminimalizowałem to do jednego powtarzanie, nie mogłem zobaczyć, jak zrobić to jeszcze mniej ze względu na dwa problemy:
-
JVM inicjalizacja obiektu/klasy dla tego konkretnego modelu obiektu / przypadku jest niezdefiniowana (zobacz ten wątek Stoskoverflow)
- zawartość zwracana z metody
getClass.getDeclaredClasses
mA niezdefiniowaną kolejność (i jest mało prawdopodobne, aby była w tej samej kolejności, co deklaracjecase object
w kodzie źródłowym)
Biorąc pod uwagę te dwa problemy, musiałem zrezygnować z próby wygenerowania domniemanej zamawianie i musiał wyraźnie wymagać od klienta zdefiniowania i zadeklarowania go za pomocą pewnego rodzaju uporządkowanego pojęcia zestawu. Ponieważ Kolekcje Scali nie mają implementacji uporządkowanego zestawu insert, najlepsze, co mogłem zrobić, to użyć List
, a następnie sprawdzić, czy naprawdę jest to zestaw. Nie tak wolałabym to osiągnąć.
I biorąc pod uwagę projekt wymagał tej drugiej listy / kolejności zestawów val
, biorąc pod uwagę przykład ChessPiecesEnhancedDecorated
powyżej, można było dodać case object PAWN2 extends Member
, a następnie zapomnieć o dodaniu Decoration(PAWN2,'P2', 2)
do decorationOrderedSet
. Tak więc, istnieje kontrola runtime, aby sprawdzić, czy lista nie jest tylko zestawem, ale zawiera wszystkie obiekty case, które rozszerzają sealed trait Member
. To była specjalna forma refleksji / makro piekła do pracy.
Proszę zostawić komentarze i / lub opinię na temat Gist .
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:26
Zalety używania klas case nad wyliczeniami to:
- w przypadku użycia klas zamkniętych, kompilator Scala może stwierdzić, czy dopasowanie jest w pełni określone, np. kiedy wszystkie możliwe dopasowania są uwzględnione w deklaracji dopasowania. Z wyliczeniami kompilator Scali nie może tego stwierdzić.
- klasy Case naturalnie obsługują więcej pól niż wyliczanie oparte na wartości, które obsługuje nazwę i ID.
Zalety używania wyliczeń zamiast klas case są:
- wyliczenia będą zazwyczaj mniej kodu do napisania.
- wyliczenia są nieco łatwiejsze do zrozumienia dla kogoś nowego w Scali, ponieważ są powszechne w innych językach
Tak więc ogólnie, jeśli potrzebujesz tylko listy prostych stałych po nazwie, użyj wyliczeń. W przeciwnym razie, jeśli potrzebujesz czegoś bardziej skomplikowanego lub chcesz, aby dodatkowe bezpieczeństwo kompilatora mówiło ci, jeśli masz wszystkie dopasowania określone, użyj klas przypadków.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-14 08:01:18
UPDATE: poniższy kod ma błąd, opisany tutaj. Poniższy program testowy działa, ale jeśli miałbyś użyć DayOfWeek.Mon (na przykład) przed samym DayOfWeek, nie powiedzie się, ponieważ DayOfWeek nie został zainicjowany(użycie obiektu wewnętrznego nie powoduje inicjalizacji obiektu zewnętrznego). Możesz nadal używać tego kodu, jeśli zrobisz coś w rodzaju val enums = Seq( DayOfWeek )
w swojej klasie głównej, wymuszając inicjalizację swoich enum, lub możesz użyć modyfikacji chaotic3quilibrium. Czekamy na makro-based enum!
Jeśli Chcesz
- ostrzeżenia o niewyczerpujących dopasowaniach wzorców
- identyfikator Int przypisany do każdej wartości enum, którą można opcjonalnie sterować
- niezmienna Lista wartości enum, w kolejności, w jakiej zostały zdefiniowane
- niezmienna Mapa od nazwy do wartości enum
- niezmienna Mapa od id do wartości enum
- miejsca do przyklejania metod/danych dla wszystkich lub poszczególnych wartości enum, lub dla enum jako całość
- uporządkowane wartości enum (dzięki czemu można sprawdzić, na przykład, czy dzień
- możliwość rozszerzenia jednego enum, aby utworzyć inne
Następnie następujące mogą być interesujące. Opinie mile widziane.
W tej implementacji znajdują się abstrakcyjne klasy bazowe Enum i EnumVal, które rozszerzasz. Za chwilę zobaczymy te klasy, ale najpierw, oto jak zdefiniujesz enum:
object DayOfWeek extends Enum {
sealed abstract class Val extends EnumVal
case object Mon extends Val; Mon()
case object Tue extends Val; Tue()
case object Wed extends Val; Wed()
case object Thu extends Val; Thu()
case object Fri extends Val; Fri()
case object Sat extends Val; Sat()
case object Sun extends Val; Sun()
}
Zauważ, że musisz użyć każdej wartości enum (call its apply metoda), aby go ożywić. [Chciałbym, żeby wewnętrzne przedmioty nie były leniwe, chyba że wyraźnie o nie poproszę. Tak myślę.]
Możemy oczywiście dodać metody / dane do DayOfWeek, Val lub poszczególnych obiektów case, jeśli sobie tego życzymy.
A oto jak byś użył takiego enum:
object DayOfWeekTest extends App {
// To get a map from Int id to enum:
println( DayOfWeek.valuesById )
// To get a map from String name to enum:
println( DayOfWeek.valuesByName )
// To iterate through a list of the enum values in definition order,
// which can be made different from ID order, and get their IDs and names:
DayOfWeek.values foreach { v => println( v.id + " = " + v ) }
// To sort by ID or name:
println( DayOfWeek.values.sorted mkString ", " )
println( DayOfWeek.values.sortBy(_.toString) mkString ", " )
// To look up enum values by name:
println( DayOfWeek("Tue") ) // Some[DayOfWeek.Val]
println( DayOfWeek("Xyz") ) // None
// To look up enum values by id:
println( DayOfWeek(3) ) // Some[DayOfWeek.Val]
println( DayOfWeek(9) ) // None
import DayOfWeek._
// To compare enums as ordinals:
println( Tue < Fri )
// Warnings about non-exhaustive pattern matches:
def aufDeutsch( day: DayOfWeek.Val ) = day match {
case Mon => "Montag"
case Tue => "Dienstag"
case Wed => "Mittwoch"
case Thu => "Donnerstag"
case Fri => "Freitag"
// Commenting these out causes compiler warning: "match is not exhaustive!"
// case Sat => "Samstag"
// case Sun => "Sonntag"
}
}
Oto co dostajesz podczas kompilacji:
DayOfWeekTest.scala:31: warning: match is not exhaustive!
missing combination Sat
missing combination Sun
def aufDeutsch( day: DayOfWeek.Val ) = day match {
^
one warning found
Możesz zastąpić "day match" przez "(day: @unchecked ) match", gdzie nie chcesz takich ostrzeżeń, lub po prostu dołączyć catch-all sprawa na końcu.
Po uruchomieniu powyższego programu otrzymujesz następujące wyjście:
Map(0 -> Mon, 5 -> Sat, 1 -> Tue, 6 -> Sun, 2 -> Wed, 3 -> Thu, 4 -> Fri)
Map(Thu -> Thu, Sat -> Sat, Tue -> Tue, Sun -> Sun, Mon -> Mon, Wed -> Wed, Fri -> Fri)
0 = Mon
1 = Tue
2 = Wed
3 = Thu
4 = Fri
5 = Sat
6 = Sun
Mon, Tue, Wed, Thu, Fri, Sat, Sun
Fri, Mon, Sat, Sun, Thu, Tue, Wed
Some(Tue)
None
Some(Thu)
None
true
Zauważ, że ponieważ lista i mapy są niezmienne, możesz łatwo usunąć elementy, aby utworzyć podzbiory, bez łamania samego enum.
Oto sama klasa Enum (i EnumVal w niej):
abstract class Enum {
type Val <: EnumVal
protected var nextId: Int = 0
private var values_ = List[Val]()
private var valuesById_ = Map[Int ,Val]()
private var valuesByName_ = Map[String,Val]()
def values = values_
def valuesById = valuesById_
def valuesByName = valuesByName_
def apply( id : Int ) = valuesById .get(id ) // Some|None
def apply( name: String ) = valuesByName.get(name) // Some|None
// Base class for enum values; it registers the value with the Enum.
protected abstract class EnumVal extends Ordered[Val] {
val theVal = this.asInstanceOf[Val] // only extend EnumVal to Val
val id = nextId
def bumpId { nextId += 1 }
def compare( that:Val ) = this.id - that.id
def apply() {
if ( valuesById_.get(id) != None )
throw new Exception( "cannot init " + this + " enum value twice" )
bumpId
values_ ++= List(theVal)
valuesById_ += ( id -> theVal )
valuesByName_ += ( toString -> theVal )
}
}
}
I tutaj jest bardziej zaawansowane użycie, które kontroluje IDs i dodaje dane / metody do abstrakcji Val i do samego enum:
object DayOfWeek extends Enum {
sealed abstract class Val( val isWeekday:Boolean = true ) extends EnumVal {
def isWeekend = !isWeekday
val abbrev = toString take 3
}
case object Monday extends Val; Monday()
case object Tuesday extends Val; Tuesday()
case object Wednesday extends Val; Wednesday()
case object Thursday extends Val; Thursday()
case object Friday extends Val; Friday()
nextId = -2
case object Saturday extends Val(false); Saturday()
case object Sunday extends Val(false); Sunday()
val (weekDays,weekendDays) = values partition (_.isWeekday)
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:54:59
Mam tu prosty lib, który pozwala używać zamkniętych cech/klas jako wartości enum bez konieczności utrzymywania własnej listy wartości. Opiera się na prostym makrze, które nie jest zależne od buggy knownDirectSubclasses
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-16 13:11:42
Aktualizacja Marzec 2017: jak skomentował Anthony Accioly , PR scala.Enumeration/enum
został zamknięty.
Dotty (kompilator nowej generacji dla Scali) przejmie prowadzenie, choć dotty issue 1970 i Martin Odersky ' S PR 1958 .
Uwaga :obecnie (sierpień 2016, 6+ lat później) propozycja usunięcia scala.Enumeration
: PR 5352
Deprecate
scala.Enumeration
, adnotacja@enum
The składnia
@enum
class Toggle {
ON
OFF
}
Jest możliwym przykładem implementacji, intencją jest również wsparcie ADT, które są zgodne z pewnymi ograniczeniami( brak zagnieżdżenia, rekurencji lub zmieniających się parametrów konstruktora), np.:
@enum
sealed trait Toggle
case object ON extends Toggle
case object OFF extends Toggle
Deprecates the unmittiated disaster that is
scala.Enumeration
.Zalety @enum nad scalą.Wyliczenie:
- faktycznie działa
- Java interop
- brak problemów z usunięciem
- No confusing mini-DSL aby dowiedzieć się, kiedy definiować wyliczenia
Wady: Brak.
To rozwiązuje problem braku możliwości posiadania jednej bazy kodowej, która obsługuje Scala-JVM,
Scala.js
i Scala-Native (kod źródłowy Javy nie jest obsługiwany naScala.js/Scala-Native
, kod źródłowy Scali nie jest w stanie zdefiniować enum, które są akceptowane przez istniejące API w Scala-JVM).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:25
Kolejna wada klas przypadków kontra wyliczeń, kiedy trzeba będzie iterację lub filtrowanie wszystkich instancji. Jest to wbudowana możliwość wyliczania (oraz wyliczeń Javy), podczas gdy klasy case nie obsługują automatycznie takiej możliwości.
Innymi słowy: "nie ma łatwego sposobu, aby uzyskać listę całkowitego zestawu wartości z klasami case".
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-09 09:18:12
Jeśli poważnie myślisz o zachowaniu interoperacyjności z innymi językami JVM (np. Działają one przejrzyście zarówno z kodu Scali, jak i Javy, co jest czymś więcej niż można powiedzieć o obiektach scala.Enumeration
czy case. Nie posiadajmy nowej biblioteki wyliczeń dla każdego nowego projektu hobbystycznego na Githubie, jeśli można tego uniknąć!
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-09 19:16:54
Widziałem różne wersje tworzenia klasy przypadków naśladującej wyliczenie. Oto moja wersja:
trait CaseEnumValue {
def name:String
}
trait CaseEnum {
type V <: CaseEnumValue
def values:List[V]
def unapply(name:String):Option[String] = {
if (values.exists(_.name == name)) Some(name) else None
}
def unapply(value:V):String = {
return value.name
}
def apply(name:String):Option[V] = {
values.find(_.name == name)
}
}
Który pozwala na konstruowanie klas przypadków, które wyglądają następująco:
abstract class Currency(override name:String) extends CaseEnumValue {
}
object Currency extends CaseEnum {
type V = Site
case object EUR extends Currency("EUR")
case object GBP extends Currency("GBP")
var values = List(EUR, GBP)
}
Może ktoś mógłby wymyślić lepszy trik niż zwykłe dodawanie każdej klasy przypadków do listy, tak jak ja. To było wszystko, co mogłem wymyślić w tym czasie.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-27 20:22:51
Ostatnio kilka razy używałem tych dwóch opcji w tę i z powrotem. Do niedawna preferowałem opcję sealed trait / case object.
1) Deklaracja Wyliczeń Scala
object OutboundMarketMakerEntryPointType extends Enumeration {
type OutboundMarketMakerEntryPointType = Value
val Alpha, Beta = Value
}
2) Sealed Traits + Case Objects
sealed trait OutboundMarketMakerEntryPointType
case object AlphaEntryPoint extends OutboundMarketMakerEntryPointType
case object BetaEntryPoint extends OutboundMarketMakerEntryPointType
Podczas gdy żadna z nich nie spełnia wszystkich tego, co daje Ci wyliczenie Javy, poniżej znajdują się zalety i wady:
Wyliczenie Scala
Plusy: - Funkcje do tworzenia instancji z opcją lub bezpośrednio zakładając dokładne (łatwiejsze przy ładowaniu z trwałego magazynu) -Wspierana jest iteracja nad wszystkimi możliwymi wartościami
Cons: -Ostrzeżenie kompilacji dla niewyczerpującego wyszukiwania nie jest obsługiwane (sprawia, że dopasowanie wzorca jest mniej idealne)
Case Objects / Sealed traits
Plusy: - Używając cech zamkniętych, możemy wstępnie utworzyć instancję niektórych wartości, podczas gdy inne mogą być wstrzykiwane w czasie tworzenia -pełna obsługa dopasowywania wzorców (zdefiniowane metody apply/unaply)
Cons: - Tworzenie instancji z persistent store-często musisz użyć dopasowania wzorca lub zdefiniować własną listę wszystkich możliwych wartości "enum"
To, co ostatecznie skłoniło mnie do zmiany mojej opinii, było czymś w rodzaju poniższego fragmentu:
object DbInstrumentQueries {
def instrumentExtractor(tableAlias: String = "s")(rs: ResultSet): Instrument = {
val symbol = rs.getString(tableAlias + ".name")
val quoteCurrency = rs.getString(tableAlias + ".quote_currency")
val fixRepresentation = rs.getString(tableAlias + ".fix_representation")
val pointsValue = rs.getInt(tableAlias + ".points_value")
val instrumentType = InstrumentType.fromString(rs.getString(tableAlias +".instrument_type"))
val productType = ProductType.fromString(rs.getString(tableAlias + ".product_type"))
Instrument(symbol, fixRepresentation, quoteCurrency, pointsValue, instrumentType, productType)
}
}
object InstrumentType {
def fromString(instrumentType: String): InstrumentType = Seq(CurrencyPair, Metal, CFD)
.find(_.toString == instrumentType).get
}
object ProductType {
def fromString(productType: String): ProductType = Seq(Commodity, Currency, Index)
.find(_.toString == productType).get
}
Wywołania .get
były ohydne - używając wyliczenia zamiast tego mogę po prostu wywołać metodę withName na wyliczeniu w następujący sposób:
object DbInstrumentQueries {
def instrumentExtractor(tableAlias: String = "s")(rs: ResultSet): Instrument = {
val symbol = rs.getString(tableAlias + ".name")
val quoteCurrency = rs.getString(tableAlias + ".quote_currency")
val fixRepresentation = rs.getString(tableAlias + ".fix_representation")
val pointsValue = rs.getInt(tableAlias + ".points_value")
val instrumentType = InstrumentType.withNameString(rs.getString(tableAlias + ".instrument_type"))
val productType = ProductType.withName(rs.getString(tableAlias + ".product_type"))
Instrument(symbol, fixRepresentation, quoteCurrency, pointsValue, instrumentType, productType)
}
}
Więc myślę, że preferuję używać wyliczeń, gdy wartości mają być dostępne z repozytorium I case objects / sealed traits inaczej.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-29 18:04:11
Wolę case objects
(to kwestia osobistych preferencji). Aby poradzić sobie z problemami związanymi z tym podejściem (parse string i iterate nad wszystkimi elementami), dodałem kilka linii, które nie są idealne, ale są skuteczne.
Wklejam ci tutaj Kod oczekując, że może być przydatny, a także, że inni mogą go poprawić.
/**
* Enum for Genre. It contains the type, objects, elements set and parse method.
*
* This approach supports:
*
* - Pattern matching
* - Parse from name
* - Get all elements
*/
object Genre {
sealed trait Genre
case object MALE extends Genre
case object FEMALE extends Genre
val elements = Set (MALE, FEMALE) // You have to take care this set matches all objects
def apply (code: String) =
if (MALE.toString == code) MALE
else if (FEMALE.toString == code) FEMALE
else throw new IllegalArgumentException
}
/**
* Enum usage (and tests).
*/
object GenreTest extends App {
import Genre._
val m1 = MALE
val m2 = Genre ("MALE")
assert (m1 == m2)
assert (m1.toString == "MALE")
val f1 = FEMALE
val f2 = Genre ("FEMALE")
assert (f1 == f2)
assert (f1.toString == "FEMALE")
try {
Genre (null)
assert (false)
}
catch {
case e: IllegalArgumentException => assert (true)
}
try {
Genre ("male")
assert (false)
}
catch {
case e: IllegalArgumentException => assert (true)
}
Genre.elements.foreach { println }
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-06 22:25:44
Dla tych, którzy wciąż szukają odpowiedzi GatesDa do pracy : Możesz po prostu odwołać się do obiektu case po zadeklarowaniu go do utworzenia jego instancji:
trait Enum[A] {
trait Value { self: A =>
_values :+= this
}
private var _values = List.empty[A]
def values = _values
}
sealed trait Currency extends Currency.Value
object Currency extends Enum[Currency] {
case object EUR extends Currency;
EUR //THIS IS ONLY CHANGE
case object GBP extends Currency; GBP //Inline looks better
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:38