Apakah ada pedoman praktik terbaik tentang kapan menggunakan kelas kasus (atau objek kasus) vs memperpanjang Pencacahan di Scala?
Mereka tampaknya menawarkan beberapa manfaat yang sama.
scala
enumeration
case-class
Alex Miller
sumber
sumber
enum
(untuk pertengahan 2020).Jawaban:
Satu perbedaan besar adalah bahwa ia
Enumeration
datang dengan dukungan untuk membuat mereka dari beberapaname
String. Sebagai contoh:Maka Anda dapat melakukan:
Ini berguna ketika ingin melanjutkan enumerasi (misalnya, ke database) atau membuatnya dari data yang berada di file. Namun, saya menemukan secara umum bahwa enumerasi agak canggung di Scala dan memiliki rasa add-on yang canggung, jadi saya sekarang cenderung menggunakan
case object
s. Acase object
lebih fleksibel daripada enum:Jadi sekarang saya memiliki keunggulan ...
Seperti yang ditunjukkan @ chaotic3quilibrium (dengan beberapa koreksi untuk memudahkan membaca):
Untuk menindaklanjuti jawaban lain di sini, kelemahan utama
case object
s overEnumeration
adalah:Tidak dapat mengulangi semua contoh "enumerasi" . Ini memang masalahnya, tetapi dalam praktiknya sangat jarang hal ini diperlukan.
Tidak dapat membuat instantiasi dengan mudah dari nilai yang ada . Ini juga benar tetapi, kecuali dalam kasus enumerasi besar (misalnya, semua mata uang), ini tidak menghadirkan overhead yang besar.
sumber
trade.ccy
dengan contoh sifat yang disegel.case
object
menghasilkan jejak kode yang lebih besar (~ 4x) dariEnumeration
? Perbedaan yang berguna terutama untukscala.js
proyek - proyek yang membutuhkan jejak kecil.UPDATE: Solusi berbasis makro baru telah dibuat yang jauh lebih unggul daripada solusi yang saya uraikan di bawah ini. Saya sangat merekomendasikan menggunakan solusi berbasis makro baru ini . Dan tampaknya rencana untuk Dotty akan membuat gaya solusi enum bagian dari bahasa. Whoohoo!
Ringkasan:
Ada tiga pola dasar untuk mencoba mereproduksi Jawa
Enum
dalam proyek Scala. Dua dari tiga pola; langsung menggunakan JavaEnum
danscala.Enumeration
, tidak mampu mengaktifkan pencocokan pola lengkap Scala. Dan yang ketiga; "disegel sifat + objek kasus", memang ... tetapi memiliki inisialisasi kelas / objek JVM yang menghasilkan generasi indeks ordinal yang tidak konsisten.Saya telah menciptakan solusi dengan dua kelas; Enumeration and EnumerationDecorated , terletak di Intisari ini . Saya tidak memposting kode ke utas ini karena file untuk Enumerasi cukup besar (+400 baris - berisi banyak komentar yang menjelaskan konteks implementasi).
Detail:
Pertanyaan yang Anda ajukan cukup umum; "... kapan menggunakan
case
kelasobjects
vs memperluas[scala.]Enumeration
". Dan ternyata ada BANYAK kemungkinan jawaban, masing-masing jawaban tergantung pada seluk-beluk persyaratan proyek spesifik yang Anda miliki. Jawabannya dapat direduksi menjadi tiga pola dasar.Untuk memulai, mari kita pastikan kita bekerja dari ide dasar yang sama tentang apa itu enumerasi. Mari kita mendefinisikan enumerasi sebagian besar dalam hal yang
Enum
disediakan pada Java 5 (1.5) :Enum
, akan lebih baik untuk dapat secara eksplisit meningkatkan kecocokan pola pencocokan Scala untuk enumerasiSelanjutnya, mari kita lihat versi tiga pola solusi yang paling umum diposting:
A) Sebenarnya langsung menggunakan pola Java
Enum
(dalam proyek Scala / Java campuran):Item berikut dari definisi enumerasi tidak tersedia:
Untuk proyek saya saat ini, saya tidak mendapat manfaat dari mengambil risiko di sekitar jalur proyek campuran Scala / Java. Dan bahkan jika saya bisa memilih untuk melakukan proyek campuran, item 7 sangat penting untuk memungkinkan saya menangkap masalah waktu kompilasi jika / ketika saya menambah / menghapus anggota enumerasi, atau sedang menulis beberapa kode baru untuk menangani anggota enumerasi yang ada.
B) Menggunakan pola "
sealed trait
+case objects
":Item berikut dari definisi enumerasi tidak tersedia:
Dapat diperdebatkan bahwa itu benar-benar memenuhi item definisi enumerasi 5 dan 6. Untuk 5, sangat sulit untuk mengklaim itu efisien. Untuk 6, itu tidak mudah untuk memperluas untuk menyimpan data terkait singleton-ness tambahan.
C) Menggunakan
scala.Enumeration
pola (terinspirasi oleh jawaban StackOverflow ini ):Item berikut dari definisi enumerasi tidak tersedia (kebetulan identik dengan daftar untuk langsung menggunakan Java Enum):
Sekali lagi untuk proyek saya saat ini, item 7 sangat penting untuk memungkinkan saya menangkap masalah waktu kompilasi jika / ketika saya menambah / menghapus anggota enumerasi, atau sedang menulis beberapa kode baru untuk berurusan dengan anggota enumerasi yang ada.
Jadi, mengingat definisi enumerasi di atas, tidak satu pun dari tiga solusi di atas yang berfungsi karena mereka tidak memberikan semua yang diuraikan dalam definisi enumerasi di atas:
Masing-masing solusi ini pada akhirnya dapat dikerjakan ulang / diperluas / di refactored untuk mencoba menutupi beberapa kebutuhan yang hilang dari masing-masing. Namun, baik Java
Enum
maupunscala.Enumeration
solusi tidak dapat diperluas secara memadai untuk menyediakan item 7. Dan untuk proyek saya sendiri, ini adalah salah satu nilai yang lebih menarik dari penggunaan tipe tertutup dalam Scala. Saya sangat suka mengkompilasi peringatan waktu / kesalahan untuk menunjukkan bahwa saya memiliki celah / masalah dalam kode saya sebagai lawan harus mengumpulkannya dari pengecualian / kegagalan runtime produksi.Dalam hal itu, saya mulai bekerja dengan
case object
jalur untuk melihat apakah saya bisa menghasilkan solusi yang mencakup semua definisi enumerasi di atas. Tantangan pertama adalah mendorong inti dari masalah inisialisasi kelas / objek JVM (dibahas secara rinci dalam posting StackOverflow ini ). Dan saya akhirnya bisa menemukan solusi.Karena solusi saya adalah dua sifat; Enumeration dan EnumerationDecorated , dan karena
Enumeration
sifatnya lebih dari +400 baris (banyak komentar yang menjelaskan konteks), saya tidak mau menempelkannya ke dalam utas ini (yang akan membuatnya meregangkan halaman secara serius). Untuk detailnya, silakan langsung menuju Intisari .Inilah solusi yang akhirnya tampak seperti menggunakan ide data yang sama seperti di atas (versi yang sepenuhnya dikomentari tersedia di sini ) dan diimplementasikan dalam
EnumerationDecorated
.Ini adalah contoh penggunaan sepasang sifat enumerasi baru yang saya buat (terletak di Intisari ini ) untuk mengimplementasikan semua kemampuan yang diinginkan dan diuraikan dalam definisi enumerasi.
Satu keprihatinan yang diungkapkan adalah bahwa nama anggota enumerasi harus diulang (
decorationOrderedSet
dalam contoh di atas). Sementara saya meminimalkannya menjadi satu pengulangan, saya tidak bisa melihat bagaimana membuatnya lebih sedikit karena dua masalah:getClass.getDeclaredClasses
memiliki urutan yang tidak ditentukan (dan sangat tidak mungkin berada dalam urutan yang sama dengancase object
deklarasi dalam kode sumber)Dengan adanya dua masalah ini, saya harus berhenti berusaha untuk menghasilkan pemesanan tersirat dan harus secara eksplisit mengharuskan klien menentukan dan menyatakannya dengan semacam gagasan kumpulan pesanan. Karena koleksi Scala tidak memiliki implementasi implementasi urutan yang disisipkan, yang terbaik yang bisa saya lakukan adalah menggunakan
List
dan kemudian memeriksa runtime apakah itu benar-benar sebuah set. Bukan bagaimana saya lebih suka untuk mencapai ini.Dan mengingat desain yang diperlukan daftar kedua / set pemesanan ini
val
, mengingatChessPiecesEnhancedDecorated
contoh di atas, adalah mungkin untuk menambahkancase object PAWN2 extends Member
dan kemudian lupa untuk menambahkanDecoration(PAWN2,'P2', 2)
kedecorationOrderedSet
. Jadi, ada pemeriksaan runtime untuk memverifikasi bahwa daftar tersebut tidak hanya satu set, tetapi berisi SEMUA objek kasus yang memperpanjangsealed trait Member
. Itu adalah bentuk khusus dari refleksi / makro neraka untuk dikerjakan.Silakan tinggalkan komentar dan / atau umpan balik pada Intisari .
sumber
org.scalaolio.util.Enumeration
danorg.scalaolio.util.EnumerationDecorated
: scalaolio.orgObjek case sudah mengembalikan nama mereka untuk metode toString mereka, jadi meneruskannya secara terpisah tidak perlu. Berikut adalah versi yang mirip dengan jho (metode kenyamanan dihilangkan untuk singkatnya):
Objek malas; dengan menggunakan vals, kita dapat menjatuhkan daftar tetapi harus mengulangi namanya:
Jika Anda tidak keberatan melakukan kecurangan, Anda dapat memuat nilai enumerasi menggunakan API refleksi atau sesuatu seperti Google Refleksi. Objek kasing yang tidak malas memberi Anda sintaks terbersih:
Bagus dan bersih, dengan semua keunggulan kelas kasus dan enumerasi Java. Secara pribadi, saya mendefinisikan nilai enumerasi di luar objek agar lebih cocok dengan kode Scala idiomatik:
sumber
Currency.values
, saya hanya mendapatkan kembali nilai yang telah saya akses sebelumnya. Apakah ada jalan keluarnya?Keuntungan menggunakan kelas kasus dibandingkan Enumerasi adalah:
Keuntungan menggunakan Enumerasi daripada kelas kasus adalah:
Jadi secara umum, jika Anda hanya perlu daftar konstanta sederhana dengan nama, gunakan enumerasi. Kalau tidak, jika Anda membutuhkan sesuatu yang sedikit lebih kompleks atau ingin keamanan tambahan dari kompiler memberi tahu Anda jika Anda memiliki semua kecocokan yang ditentukan, gunakan kelas kasus.
sumber
UPDATE: Kode di bawah ini memiliki bug, dijelaskan di sini . Program pengujian di bawah ini berfungsi, tetapi jika Anda menggunakan DayOfWeek.Mon (misalnya) sebelum DayOfWeek itu sendiri, itu akan gagal karena DayOfWeek belum diinisialisasi (penggunaan objek dalam tidak menyebabkan objek luar diinisialisasi). Anda masih dapat menggunakan kode ini jika Anda melakukan sesuatu seperti
val enums = Seq( DayOfWeek )
di kelas utama Anda, memaksa inisialisasi enum Anda, atau Anda dapat menggunakan modifikasi chaotic3quilibrium. Menantikan enum berbasis makro!jika kamu mau
maka yang berikut ini mungkin menarik. Umpan balik.
Dalam implementasi ini ada kelas dasar Enum dan EnumVal abstrak, yang Anda memperpanjang. Kita akan melihat kelas-kelas itu sebentar lagi, tetapi pertama-tama, inilah cara Anda mendefinisikan enum:
Perhatikan bahwa Anda harus menggunakan setiap nilai enum (panggil metode yang berlaku) untuk menghidupkannya. [Aku berharap benda dalam tidak malas kecuali aku secara khusus memintanya. Kupikir.]
Kita tentu saja dapat menambahkan metode / data ke DayOfWeek, Val, atau objek kasus individual jika kita inginkan.
Dan inilah cara Anda menggunakan enum seperti itu:
Inilah yang Anda dapatkan ketika Anda mengompilasinya:
Anda dapat mengganti "pertandingan hari" dengan "pertandingan" (hari: @ cek ulang) "di mana Anda tidak ingin peringatan seperti itu, atau cukup sertakan kotak semua kasus di akhir.
Ketika Anda menjalankan program di atas, Anda mendapatkan output ini:
Perhatikan bahwa karena Daftar dan Peta tidak dapat diubah, Anda dapat dengan mudah menghapus elemen untuk membuat subset, tanpa merusak enum itu sendiri.
Ini adalah kelas Enum itu sendiri (dan EnumVal di dalamnya):
Dan di sini adalah penggunaan yang lebih maju yang mengontrol ID dan menambahkan data / metode ke abstraksi Val dan enum itu sendiri:
sumber
var
] adalah dosa berat batas di dunia FP" - Saya tidak berpikir bahwa pendapat diterima secara universal.Saya memiliki lib sederhana yang bagus di sini yang memungkinkan Anda untuk menggunakan sifat / kelas yang disegel sebagai nilai enum tanpa harus mempertahankan daftar nilai Anda sendiri. Itu bergantung pada makro sederhana yang tidak bergantung pada buggy
knownDirectSubclasses
.https://github.com/lloydmeta/enumeratum
sumber
Pembaruan Maret 2017: seperti yang dikomentari oleh Anthony Accioly ,
scala.Enumeration/enum
PR telah ditutup.Dotty (kompiler generasi berikutnya untuk Scala) akan memimpin, meskipun edisi dotty 1970 dan PR 1958 Martin Odersky .
Catatan: sekarang ada (Agustus 2016, 6+ tahun kemudian) proposal untuk dihapus
scala.Enumeration
: PR 5352sumber
Kerugian lain dari kelas kasus versus Enumerasi ketika Anda harus mengulang atau memfilter semua contoh. Ini adalah kemampuan Enumerasi bawaan (dan enum Java juga) sementara kelas kasus tidak secara otomatis mendukung kemampuan tersebut.
Dengan kata lain: "tidak ada cara mudah untuk mendapatkan daftar set total nilai yang disebutkan dengan kelas kasus".
sumber
Jika Anda serius mempertahankan interoperabilitas dengan bahasa JVM lainnya (mis. Java) maka opsi terbaik adalah menulis Java enum. Mereka bekerja secara transparan dari kedua Scala dan kode Java, yang lebih dari yang bisa dikatakan untuk
scala.Enumeration
atau objek kasus. Mari kita tidak memiliki perpustakaan enumerasi baru untuk setiap proyek hobi baru di GitHub, jika itu bisa dihindari!sumber
Saya telah melihat berbagai versi membuat kelas kasus meniru enumerasi. Ini versi saya:
Yang memungkinkan Anda untuk membangun kelas kasus yang terlihat seperti berikut:
Mungkin seseorang bisa membuat trik yang lebih baik daripada hanya menambahkan kelas kasus masing-masing ke daftar seperti yang saya lakukan. Hanya itu yang bisa saya pikirkan saat itu.
sumber
Saya telah bolak-balik pada dua opsi ini beberapa kali terakhir saya membutuhkannya. Hingga baru-baru ini, preferensi saya adalah untuk opsi objek sifat / kasus tersegel.
1) Deklarasi Pencacahan Scala
2) Ciri Tertutup + Objek Kasus
Meskipun tidak satu pun dari ini benar-benar memenuhi semua yang diberikan enumerasi java kepada Anda, di bawah ini adalah pro dan kontra:
Pencacahan Scala
Kelebihan: -Fungsi untuk instantiating dengan opsi atau langsung mengasumsikan akurat (lebih mudah saat memuat dari toko yang persisten) -Iterasi atas semua nilai yang mungkin didukung
Kekurangan: -Peringatan kompilasi untuk pencarian yang tidak lengkap tidak didukung (membuat pencocokan pola kurang ideal)
Objek Kasus / Ciri tertutup
Kelebihan: -Menggunakan ciri-ciri tersegel, kita dapat melakukan pra-instantiasi beberapa nilai sementara yang lain dapat disuntikkan pada waktu pembuatan -Dukungan penuh untuk pencocokan pola (berlaku / metode tidak berlaku didefinisikan)
Cons: -Instantiating dari toko persisten - Anda sering harus menggunakan pencocokan pola di sini atau menentukan daftar Anda sendiri dari semua 'enum values' yang mungkin
Apa yang akhirnya membuat saya mengubah pendapat saya adalah seperti cuplikan berikut:
The
.get
panggilan yang mengerikan - menggunakan pencacahan bukan saya hanya bisa memanggil metode withName pada pencacahan sebagai berikut:Jadi saya pikir preferensi saya ke depan adalah menggunakan Enumerasi ketika nilai-nilai dimaksudkan untuk diakses dari repositori dan objek kasus / sifat tertutup jika tidak.
sumber
Saya lebih suka
case objects
(ini masalah preferensi pribadi). Untuk mengatasi masalah yang melekat pada pendekatan itu (parse string dan iterate atas semua elemen), saya telah menambahkan beberapa baris yang tidak sempurna, tetapi efektif.Saya menempelkan Anda kode di sini mengharapkan itu bisa bermanfaat, dan juga bahwa orang lain dapat memperbaikinya.
sumber
Bagi mereka yang masih mencari cara untuk mendapatkan jawaban GatesDa untuk bekerja : Anda bisa mereferensikan objek kasus setelah mendeklarasikannya untuk membuat instance:
sumber
Saya pikir keuntungan terbesar dari memiliki
case classes
lebih dari ituenumerations
adalah Anda dapat menggunakan pola tipe class alias ad-hoc polymorphysm . Tidak perlu mencocokkan enum seperti:alih-alih, Anda akan memiliki sesuatu seperti:
sumber