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.

Author: Jacek Laskowski, 2009-12-14

13 answers

Jedną dużą różnicą jest to, że Enumerationsą 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 typu Currency 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 typem Option[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ą:

  1. nie można iterować wszystkich instancji "wyliczenia" . Z pewnością tak jest, ale w praktyce okazało się, że jest to niezwykle rzadkie.

  2. 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.

 205
Author: oxbow_lakes,
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
 60
Author: GatesDA,
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ć caseklasyobjects 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):

  1. zawiera naturalnie uporządkowany zamknięty zbiór nazwanych członków
    1. istnieje stała liczba członków
    2. członkowie są naturalnie uporządkowani i wyraźnie indeksowani
        W przeciwieństwie do sortowania w oparciu o pewne kryteria kwerendy członka inate
      • ]}
    3. każdy członek ma unikalną nazwę w total set of all members
  2. wszyscy członkowie mogą być łatwo iterowani na podstawie ich indeksów
  3. członek może być pobrany z jego (rozróżniającą wielkość liter) nazwą
    1. byłoby całkiem miło, gdyby członek mógł być również pobrany z jego nazwą bez rozróżniania wielkości liter
  4. członek może być pobrany z jego indeksem
  5. Użytkownicy mogą łatwo, przejrzyście i efektywnie korzystać z serializacji.]}
  6. 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:

  1. 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
  2. 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. 1.2 - członkowie są naturalnie uporządkowani i wyraźnie indeksowani
  2. 2 - wszyscy członkowie mogą być łatwo iterowane na podstawie ich indeksów
  3. 3-element może być pobrany z jego nazwą (rozróżniającą wielkość liter)
  4. 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
  5. 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):
  1. 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
  2. 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:

  1. Java Enum bezpośrednio w mieszanym projekcie Scala / Java
  2. "sealed trait + Case objects"
  3. 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)
  1. zawartość zwracana z metody getClass.getDeclaredClasses mA niezdefiniowaną kolejność (i jest mało prawdopodobne, aby była w tej samej kolejności, co deklaracje case 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 .

 58
Author: chaotic3quilibrium,
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.

 26
Author: Aaron,
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)
}
 15
Author: AmigoNico,
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.

Https://github.com/lloydmeta/enumeratum

 11
Author: lloydmeta,
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 na Scala.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).

 10
Author: VonC,
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".

 8
Author: user142435,
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ąć!

 5
Author: Connor Doyle,
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.

 4
Author: jho,
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.

 2
Author: Mad Dog,
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 }
}
 2
Author: jamming,
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
}
 0
Author: V-Lamp,
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