Czy istnieją jakieś wytyczne dotyczące najlepszych praktyk dotyczące tego, kiedy używać klas przypadków (lub obiektów spraw) zamiast rozszerzania Wyliczania w Scali?
Wydaje się, że oferują te same korzyści.
scala
enumeration
case-class
Alex Miller
źródło
źródło
enum
na Dotty (z połowy 2020 r.).Odpowiedzi:
Jedną dużą różnicą jest to, że
Enumeration
są obsługiwane przez tworzenie ich z jakiegośname
Stringa. Na przykład:Następnie możesz zrobić:
Jest to przydatne, gdy chcesz zachować wyliczenia (na przykład w bazie danych) lub utworzyć je z danych znajdujących się w plikach. Jednak ogólnie uważam, że wyliczenia są nieco nieporadne w Scali i mam wrażenie niezręcznego dodatku, więc teraz mam tendencję do używania
case object
s. Acase object
jest bardziej elastyczny niż wyliczenie:Więc teraz mam tę zaletę ...
As @ chaotic3quilibrium wskazał (z pewnymi poprawkami ułatwiającymi czytanie):
Aby odpowiedzieć na inne odpowiedzi tutaj, główne wady
case object
s ponadEnumeration
s to:Nie można iterować we wszystkich przypadkach „wyliczenia” . Z pewnością tak jest, ale w praktyce stwierdziłem, że jest to niezwykle rzadkie.
Nie można łatwo utworzyć instancji z utrwalonej wartości . Jest to również prawdą, ale z wyjątkiem dużych wyliczeń (na przykład wszystkich walut), nie stanowi to dużego obciążenia.
źródło
trade.ccy
w przykładzie zapieczętowanej cechy.case
object
generują większego (~ 4x) śladu kodu niżEnumeration
? Przydatne rozróżnienie, szczególnie w przypadkuscala.js
projektów wymagających małej powierzchni.AKTUALIZACJA: Utworzono nowe rozwiązanie oparte na makrach , które jest znacznie lepsze niż rozwiązanie, które przedstawiam poniżej. Zdecydowanie polecam korzystanie z tego nowego rozwiązania opartego na makrach . I wydaje się, że plany Dotty sprawią, że ten styl rozwiązania enum będzie częścią języka. Whoohoo!
Podsumowanie:
Istnieją trzy podstawowe wzorce próby odtworzenia Javy
Enum
w ramach projektu Scala. Dwa z trzech wzorów; bezpośrednio przy użyciu JavaEnum
iscala.Enumeration
nie są w stanie umożliwić wyczerpującego dopasowania wzorca Scali. I trzeci; „Sealed trait + case object”, ma… ale ma komplikacje związane z inicjalizacją klasy / obiektu JVM, co powoduje niespójne generowanie indeksu porządkowego.Stworzyłem rozwiązanie z dwiema klasami; Wyliczenie i wyliczenie Udekorowane , znajdujące się w tej Gist . Nie opublikowałem kodu w tym wątku, ponieważ plik wyliczenia był dość duży (+400 wierszy - zawiera wiele komentarzy wyjaśniających kontekst implementacji).
Szczegóły:
Pytanie, które zadajesz, jest dość ogólne; „... kiedy używać
case
klasobjects
zamiast rozszerzania[scala.]Enumeration
”. I okazuje się, że istnieje WIELE możliwych odpowiedzi, każda odpowiedź zależy od subtelności określonych wymagań projektu. Odpowiedź można zredukować do trzech podstawowych wzorów.Na początek upewnijmy się, że pracujemy od tego samego podstawowego pojęcia, czym jest wyliczenie. Zdefiniujmy wyliczenie głównie w kategoriach
Enum
dostarczonych od Java 5 (1.5) :Enum
, byłoby miło móc jawnie wykorzystać sprawdzanie wyczerpania dopasowania wzorca Scali w celu wyliczeniaNastępnie spójrzmy na sprowadzone wersje trzech najczęściej publikowanych wzorców rozwiązań:
A) Właściwie bezpośrednio przy użyciu wzorca Java
Enum
(w mieszanym projekcie Scala / Java):Następujące elementy z definicji wyliczenia nie są dostępne:
W przypadku moich bieżących projektów nie czerpię korzyści z ryzyka związanego ze ścieżką projektów mieszanych Scala / Java. I nawet gdybym mógł wybrać projekt mieszany, punkt 7 jest krytyczny, ponieważ pozwala mi wychwycić problemy z czasem kompilacji, jeśli / kiedy dodam / usunę elementy wyliczeniowe lub piszę nowy kod, aby poradzić sobie z istniejącymi elementami wyliczającymi.
B) Używając wzorca „
sealed trait
+case objects
”:Następujące elementy z definicji wyliczenia nie są dostępne:
Można argumentować, że naprawdę spełnia pozycje 5 i 6. definicji wyliczenia. W przypadku 5 trudno jest stwierdzić, że jest wydajna. W przypadku wersji 6 rozszerzenie nie jest tak naprawdę łatwe do przechowywania dodatkowych powiązanych danych dotyczących singletonowości.
C) Używając
scala.Enumeration
wzorca (zainspirowanego odpowiedzią StackOverflow ):Następujące elementy z definicji wyliczenia nie są dostępne (okazuje się, że są identyczne z listą do bezpośredniego używania Java Enum):
Ponownie w moich bieżących projektach punkt 7 ma kluczowe znaczenie dla umożliwienia mi wychwycenia problemów z czasem kompilacji, jeśli / kiedy dodam / usunę elementy wyliczeniowe lub piszę nowy kod, aby poradzić sobie z istniejącymi elementami wyliczającymi.
Biorąc pod uwagę powyższą definicję wyliczenia, żadne z powyższych trzech rozwiązań nie działa, ponieważ nie zapewniają one wszystkiego, co opisano w powyższej definicji wyliczenia:
Każde z tych rozwiązań można ostatecznie przerobić / rozszerzyć / zrefaktoryzować, aby spróbować zaspokoić niektóre z brakujących wymagań każdego z nich. Jednak ani Java,
Enum
aniscala.Enumeration
rozwiązania nie mogą być wystarczająco rozbudowane, aby zapewnić pozycję 7. A dla moich własnych projektów jest to jedna z bardziej przekonujących wartości używania zamkniętego typu w Scali. Zdecydowanie wolę kompilować ostrzeżenia / błędy czasowe, aby wskazać, że mam lukę / problem w moim kodzie, zamiast konieczności zbierania go z wyjątku / awarii środowiska wykonawczego.W tym względzie zacząłem pracować ze
case object
ścieżką, aby sprawdzić, czy mogę stworzyć rozwiązanie, które obejmowałoby wszystkie powyższe definicje wyliczeń. Pierwszym wyzwaniem było przepchnięcie rdzenia problemu inicjowania klasy / obiektu JVM (szczegółowo opisanego w tym poście StackOverflow ). I w końcu udało mi się znaleźć rozwiązanie.Ponieważ moim rozwiązaniem są dwie cechy; Wyliczanie i wyliczanie Udekorowane , a ponieważ
Enumeration
cecha ma ponad +400 linii (wiele komentarzy wyjaśniających kontekst), rezygnuję z wklejania jej do tego wątku (co spowodowałoby znaczne rozciągnięcie strony). Aby uzyskać szczegółowe informacje, przejdź bezpośrednio do Gist .Oto, jak rozwiązanie wygląda tak, używając tego samego pomysłu danych jak powyżej (w pełni skomentowana wersja dostępna tutaj ) i wdrożonego w
EnumerationDecorated
.Jest to przykładowe użycie nowej pary cech wyliczenia, które utworzyłem (znajdujących się w tej Gist ) w celu wdrożenia wszystkich możliwości pożądanych i opisanych w definicji wyliczenia.
Jednym z wyrażonych problemów jest to, że nazwy członków wyliczenia muszą zostać powtórzone (
decorationOrderedSet
w powyższym przykładzie). Chociaż zminimalizowałem to do jednego powtórzenia, nie mogłem zobaczyć, jak to zrobić jeszcze mniej z powodu dwóch problemów:getClass.getDeclaredClasses
ma niezdefiniowaną kolejność (i jest mało prawdopodobne, aby była w tej samej kolejności, cocase object
deklaracje w kodzie źródłowym)Biorąc pod uwagę te dwa problemy, musiałem zrezygnować z próby wygenerowania dorozumianego zamówienia i musiałem wyraźnie wymagać od klienta zdefiniowania i zadeklarowania go za pomocą jakiegoś uporządkowanego pojęcia zestawu. Ponieważ kolekcje Scali nie mają implementacji zestawu z wstawionymi wstawkami, najlepsze, co mogłem zrobić, to
List
sprawdzić, a następnie sprawdzić, czy był to naprawdę zestaw. Nie tak wolałbym to osiągnąć.Biorąc pod uwagę, że projekt wymagał drugiego uporządkowania listy / zestawu
val
, biorąc pod uwagęChessPiecesEnhancedDecorated
powyższy przykład, można było dodać,case object PAWN2 extends Member
a następnie zapomnieć o dodaniuDecoration(PAWN2,'P2', 2)
dodecorationOrderedSet
. Tak więc sprawdzanie środowiska wykonawczego sprawdza, czy lista nie jest tylko zestawem, ale zawiera WSZYSTKIE obiekty sprawy, które rozszerzająsealed trait Member
. To była specjalna forma refleksji / makro piekła do przepracowania.Zostaw komentarz i / lub opinię na temat Gist .
źródło
org.scalaolio.util.Enumeration
aorg.scalaolio.util.EnumerationDecorated
: scalaolio.orgObiekty Case już zwracają swoją nazwę dla swoich metod toString, więc przekazywanie jej osobno nie jest konieczne. Oto wersja podobna do jho (metody wygody pominięte ze względu na zwięzłość):
Obiekty są leniwe; za pomocą vals zamiast tego możemy usunąć listę, ale musimy powtórzyć nazwę:
Jeśli nie masz nic przeciwko oszukiwaniu, możesz wstępnie załadować wartości wyliczenia za pomocą interfejsu API odbicia lub czegoś takiego jak Google Reflections. Nie leniwe obiekty wielkości liter zapewniają najczystszą składnię:
Ładne i czyste, ze wszystkimi zaletami klas spraw i wyliczeń Java. Osobiście definiuję wartości wyliczania poza obiektem, aby lepiej dopasować idiomatyczny kod Scala:
źródło
Currency.values
, odzyskam tylko te wartości, do których wcześniej uzyskałem dostęp. Czy jest na to jakiś sposób?Zalety korzystania z klas spraw w porównaniu z wyliczeniami to:
Korzyści z używania Wyliczeń zamiast klas spraw są:
Ogólnie rzecz biorąc, jeśli potrzebujesz tylko listy prostych stałych według nazwy, użyj wyliczeń. W przeciwnym razie, jeśli potrzebujesz czegoś bardziej złożonego lub chcesz dodatkowego bezpieczeństwa kompilatora informującego, czy masz określone wszystkie dopasowania, użyj klas przypadków.
źródło
AKTUALIZACJA: Poniższy kod zawiera błąd opisany tutaj . Poniższy program testowy działa, ale jeśli użyjesz DayOfWeek.Mon (na przykład) przed samym DayOfWeek, zakończy się niepowodzeniem, ponieważ DayOfWeek nie został zainicjowany (użycie obiektu wewnętrznego nie powoduje zainicjowania obiektu zewnętrznego). Nadal możesz używać tego kodu, jeśli robisz coś
val enums = Seq( DayOfWeek )
w swojej klasie głównej, zmuszając do inicjacji swoich wyliczeń lub możesz użyć modyfikacji chaotycznego 3quilibrium. Czekamy na wyliczenie oparte na makrach!Jeśli chcesz
wówczas mogą być interesujące następujące rzeczy. Witamy mile widziane.
W tej implementacji istnieją abstrakcyjne klasy podstawowe Enum i EnumVal, które rozszerzamy. Za chwilę zobaczymy te klasy, ale najpierw oto, jak zdefiniujesz wyliczenie:
Zauważ, że musisz użyć każdej wartości wyliczenia (wywołaj jej metodę zastosuj), aby wprowadzić ją w życie. [Chciałbym, żeby przedmioty wewnętrzne nie były leniwe, chyba że o to poproszę. Myślę.]
Możemy oczywiście dodawać metody / dane do obiektów DayOfWeek, Val lub indywidualnych obiektów przypadków, jeśli chcemy.
A oto jak użyłbyś takiego wyliczenia:
Oto, co otrzymujesz po skompilowaniu:
Możesz zamienić „dopasowanie dnia” na „dopasowanie dnia (dzień: @ odznaczone)”, jeśli nie chcesz takich ostrzeżeń, lub po prostu dołącz na końcu sprawę typu „catch-all”.
Po uruchomieniu powyższego programu otrzymujesz następujące dane wyjściowe:
Zauważ, że ponieważ Lista i Mapy są niezmienne, możesz łatwo usuwać elementy, aby tworzyć podzbiory, bez przerywania samego wyliczenia.
Oto sama klasa Enum (i EnumVal w niej):
A oto jego bardziej zaawansowane zastosowanie, które kontroluje identyfikatory i dodaje dane / metody do abstrakcji Val i samego wyliczenia:
źródło
var
] jest granicznym grzechem śmiertelnym w świecie FP” - nie sądzę, że opinia ta jest powszechnie akceptowana.Mam tutaj fajną prostą bibliotekę lib, która pozwala ci używać zapieczętowanych cech / klas jako wartości wyliczeniowych 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
źródło
Aktualizacja z marca 2017 r .: jak skomentował Anthony Accioly ,
scala.Enumeration/enum
PR został zamknięty.Dotty (kompilator nowej generacji dla Scali) przejmie wiodącą rolę, choć wydanie dotty 1970 i PR Martina Oderskiego 1958 .
Uwaga: istnieje teraz (sierpień 2016, ponad 6 lat później) propozycja usunięcia
scala.Enumeration
: PR 5352źródło
Kolejna wada klas spraw w porównaniu z wyliczeniami, gdy trzeba będzie iterować lub filtrować we wszystkich instancjach. Jest to wbudowana funkcja wyliczania (i także wyliczenia Java), podczas gdy klasy przypadków nie obsługują automatycznie takiej możliwości.
Innymi słowy: „nie ma łatwego sposobu na uzyskanie listy całkowitego zestawu wyliczonych wartości z klasami przypadków”.
źródło
Jeśli poważnie myślisz o utrzymaniu współdziałania z innymi językami JVM (np. Java), najlepszą opcją jest pisanie wyliczeń Java. Działają one transparentnie zarówno z kodu Scala, jak i kodu Java, co jest więcej niż można powiedzieć o
scala.Enumeration
obiektach lub przypadkach. Nie będziemy mieć nowej biblioteki wyliczeń dla każdego nowego projektu hobby na GitHub, jeśli można tego uniknąć!źródło
Widziałem różne wersje tworzenia klas przypadków naśladujących wyliczenie. Oto moja wersja:
Co pozwala budować klasy spraw, które wyglądają następująco:
Może ktoś mógłby wymyślić lepszy trik niż po prostu dodać każdą klasę spraw do listy, tak jak ja. To było wszystko, co mogłem wtedy wymyślić.
źródło
Kilka razy zastanawiałem się nad tymi dwiema opcjami, kiedy kilka razy ich potrzebowałem. Do niedawna preferowałem opcję zapieczętowanej cechy obiektu / sprawy.
1) Deklaracja wyliczenia Scala
2) Zapieczętowane cechy + przedmioty skrzynek
Chociaż żadne z nich tak naprawdę nie spełnia wszystkich warunków wyliczenia java, poniżej przedstawiono zalety i wady:
Wyliczenie Scala
Plusy: -Funkcje do tworzenia instancji z opcją lub bezpośredniego zakładania dokładności (łatwiejsze przy ładowaniu z trwałego sklepu) -Interacja wszystkich możliwych wartości jest obsługiwana
Wady: -Ostrzeżenie kompilacji dla niewyczerpującego wyszukiwania nie jest obsługiwane (sprawia, że dopasowanie wzorca jest mniej idealne)
Obiekty przedmiotów / Zapieczętowane cechy
Plusy: -Używając cech zapieczętowanych, możemy wstępnie utworzyć instancję niektórych wartości, podczas gdy inne można wstrzykiwać podczas tworzenia - pełna obsługa dopasowania wzorców (zdefiniowane metody zastosowania / zastosowania)
Minusy: -Instantingowanie z trwałego sklepu - często musisz tutaj zastosować dopasowanie wzorca lub zdefiniować własną listę wszystkich możliwych „wartości wyliczeniowych”
To, co ostatecznie skłoniło mnie do zmiany zdania, to coś w rodzaju następującego fragmentu kodu:
Te
.get
rozmowy były ohydne - stosując wyliczenie zamiast mogę po prostu wywołać metodę withName na wyliczenie w sposób następujący:Więc myślę, że preferuję używanie Wyliczeń, gdy dostęp do wartości ma być uzyskiwany z repozytorium, a w przeciwnym wypadku obiekty / cechy zapieczętowane.
źródło
wolę
case objects
(to kwestia osobistych preferencji). Aby poradzić sobie z problemami związanymi z tym podejściem (analizowanie łańcucha i iteracja po wszystkich elementach), dodałem kilka wierszy, które nie są idealne, ale są skuteczne.Wklejam tutaj kod, oczekując, że może być przydatny, a także, że inni mogą go ulepszyć.
źródło
Dla tych, którzy wciąż szukają sposobu, aby uzyskać odpowiedź GatesDa na działanie : Możesz po prostu odwołać się do obiektu sprawy po zadeklarowaniu go do utworzenia instancji:
źródło
Myślę, że największą zaletą
case classes
nadenumerations
tym jest to, że można użyć wzorca klasy typu aka polimorfizm ad-hoc . Nie musisz dopasowywać wyliczeń, takich jak:zamiast tego będziesz mieć coś takiego:
źródło