Scala tidak memiliki tipe-aman enum
seperti yang dimiliki Java. Diberikan seperangkat konstanta terkait, apa yang akan menjadi cara terbaik di Scala untuk mewakili konstanta tersebut?
Scala tidak memiliki tipe-aman enum
seperti yang dimiliki Java. Diberikan seperangkat konstanta terkait, apa yang akan menjadi cara terbaik di Scala untuk mewakili konstanta tersebut?
Jawaban:
http://www.scala-lang.org/docu/files/api/scala/Enumeration.html
Contoh penggunaan
object Main extends App {
object WeekDay extends Enumeration {
type WeekDay = Value
val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value
}
import WeekDay._
def isWorkingDay(d: WeekDay) = ! (d == Sat || d == Sun)
WeekDay.values filter isWorkingDay foreach println
}
Saya harus mengatakan bahwa contoh yang disalin dari dokumentasi Scala oleh skaffman di atas adalah utilitas terbatas dalam praktek (Anda mungkin juga menggunakan case object
s).
Untuk mendapatkan sesuatu yang paling mirip dengan Java Enum
(yaitu dengan metode yang masuk akal toString
dan valueOf
- mungkin Anda mempertahankan nilai enum ke database) Anda perlu sedikit memodifikasinya. Jika Anda menggunakan kode skaffman :
WeekDay.valueOf("Sun") //returns None
WeekDay.Tue.toString //returns Weekday(2)
Sedangkan menggunakan deklarasi berikut:
object WeekDay extends Enumeration {
type WeekDay = Value
val Mon = Value("Mon")
val Tue = Value("Tue")
... etc
}
Anda mendapatkan hasil yang lebih masuk akal:
WeekDay.valueOf("Sun") //returns Some(Sun)
WeekDay.Tue.toString //returns Tue
valueOf
pengganti withName
, yang tidak mengembalikan Opsi, dan melempar NSE jika tidak ada kecocokan. Apa itu!
Map[Weekday.Weekday, Long]
dan menambahkan nilai katakan Mon
padanya kompiler melempar kesalahan jenis tidak valid. Diharapkan hari kerja. Setiap hari nilai ditemukan? Mengapa ini terjadi?
Ada banyak cara untuk melakukannya.
1) Gunakan simbol. Namun, itu tidak akan memberi Anda keamanan jenis apa pun, selain tidak menerima non-simbol di mana simbol diharapkan. Saya hanya menyebutkannya di sini untuk kelengkapan. Berikut ini contoh penggunaannya:
def update(what: Symbol, where: Int, newValue: Array[Int]): MatrixInt =
what match {
case 'row => replaceRow(where, newValue)
case 'col | 'column => replaceCol(where, newValue)
case _ => throw new IllegalArgumentException
}
// At REPL:
scala> val a = unitMatrixInt(3)
a: teste7.MatrixInt =
/ 1 0 0 \
| 0 1 0 |
\ 0 0 1 /
scala> a('row, 1) = a.row(0)
res41: teste7.MatrixInt =
/ 1 0 0 \
| 1 0 0 |
\ 0 0 1 /
scala> a('column, 2) = a.row(0)
res42: teste7.MatrixInt =
/ 1 0 1 \
| 0 1 0 |
\ 0 0 0 /
2) Menggunakan kelas Enumeration
:
object Dimension extends Enumeration {
type Dimension = Value
val Row, Column = Value
}
atau, jika Anda perlu membuat cerita bersambung atau menampilkannya:
object Dimension extends Enumeration("Row", "Column") {
type Dimension = Value
val Row, Column = Value
}
Ini dapat digunakan seperti ini:
def update(what: Dimension, where: Int, newValue: Array[Int]): MatrixInt =
what match {
case Row => replaceRow(where, newValue)
case Column => replaceCol(where, newValue)
}
// At REPL:
scala> a(Row, 2) = a.row(1)
<console>:13: error: not found: value Row
a(Row, 2) = a.row(1)
^
scala> a(Dimension.Row, 2) = a.row(1)
res1: teste.MatrixInt =
/ 1 0 0 \
| 0 1 0 |
\ 0 1 0 /
scala> import Dimension._
import Dimension._
scala> a(Row, 2) = a.row(1)
res2: teste.MatrixInt =
/ 1 0 0 \
| 0 1 0 |
\ 0 1 0 /
Sayangnya, itu tidak memastikan bahwa semua pertandingan dicatat. Jika saya lupa memasukkan Baris atau Kolom dalam pertandingan, kompiler Scala tidak akan memperingatkan saya. Jadi itu memberi saya beberapa jenis keamanan, tetapi tidak sebanyak yang bisa diperoleh.
3) Objek kasing:
sealed abstract class Dimension
case object Row extends Dimension
case object Column extends Dimension
Sekarang, jika saya meninggalkan kasing pada match
, kompiler akan memperingatkan saya:
MatrixInt.scala:70: warning: match is not exhaustive!
missing combination Column
what match {
^
one warning found
Ini digunakan dengan cara yang hampir sama, dan bahkan tidak memerlukan import
:
scala> val a = unitMatrixInt(3)
a: teste3.MatrixInt =
/ 1 0 0 \
| 0 1 0 |
\ 0 0 1 /
scala> a(Row,2) = a.row(0)
res15: teste3.MatrixInt =
/ 1 0 0 \
| 0 1 0 |
\ 1 0 0 /
Jadi, Anda mungkin bertanya-tanya, mengapa pernah menggunakan Enumerasi alih-alih objek kasus. Faktanya, objek kasus memang memiliki kelebihan berkali-kali, seperti di sini. Kelas Enumerasi, bagaimanapun, memiliki banyak metode Koleksi, seperti elemen (iterator pada Scala 2.8), yang mengembalikan Iterator, peta, flatMap, filter, dll.
Jawaban ini pada dasarnya adalah bagian yang dipilih dari artikel ini di blog saya.
Symbol
instance tidak dapat memiliki spasi atau karakter khusus. Kebanyakan orang ketika pertama kali bertemu Symbol
kelas mungkin berpikir begitu, tetapi sebenarnya tidak benar. Symbol("foo !% bar -* baz")
kompilasi dan berjalan dengan sangat baik. Dengan kata lain Anda dapat dengan sempurna membuat Symbol
instance yang membungkus string apa pun (Anda tidak bisa melakukannya dengan gula sintaksis "koma tunggal"). Satu-satunya hal yang Symbol
menjamin adalah keunikan simbol yang diberikan, membuatnya sedikit lebih cepat untuk membandingkan dan mencocokkan.
String
, misalnya, sebagai argumen ke Symbol
parameter.
String
dengan kelas lain yang pada dasarnya adalah pembungkus di sekitar string dan dapat dikonversi secara bebas di kedua arah (seperti halnya untuk Symbol
). Saya kira itulah yang Anda maksud ketika mengatakan "Itu tidak akan memberi Anda keamanan jenis apa pun", hanya saja tidak begitu jelas mengingat bahwa OP secara eksplisit meminta solusi jenis aman. Saya tidak yakin apakah pada saat penulisan Anda tahu bahwa bukan saja itu bukan tipe aman karena itu sama sekali bukan enum, tetapi juga Symbol
tidak menjamin bahwa argumen yang disahkan tidak akan memiliki karakter khusus.
'foo
notasi khusus yang tidak menghalangi string bukan pengidentifikasi). Inilah kesalahpahaman yang ingin saya singkirkan untuk pembaca di masa depan.
Cara mendeklarasikan enumerasi dengan nama yang sedikit kurang jelas:
object WeekDay extends Enumeration("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat") {
type WeekDay = Value
val Sun, Mon, Tue, Wed, Thu, Fri, Sat = Value
}
WeekDay.valueOf("Wed") // returns Some(Wed)
WeekDay.Fri.toString // returns Fri
Tentu saja masalahnya di sini adalah bahwa Anda harus menjaga agar urutan nama dan vokal tetap sinkron yang lebih mudah dilakukan jika nama dan val dinyatakan pada baris yang sama.
Anda bisa menggunakan kelas abstrak tersegel alih-alih enumerasi, misalnya:
sealed abstract class Constraint(val name: String, val verifier: Int => Boolean)
case object NotTooBig extends Constraint("NotTooBig", (_ < 1000))
case object NonZero extends Constraint("NonZero", (_ != 0))
case class NotEquals(x: Int) extends Constraint("NotEquals " + x, (_ != x))
object Main {
def eval(ctrs: Seq[Constraint])(x: Int): Boolean =
(true /: ctrs){ case (accum, ctr) => accum && ctr.verifier(x) }
def main(args: Array[String]) {
val ctrs = NotTooBig :: NotEquals(5) :: Nil
val evaluate = eval(ctrs) _
println(evaluate(3000))
println(evaluate(3))
println(evaluate(5))
}
}
baru saja menemukan enumeratum . itu sangat menakjubkan dan sama-sama luar biasa itu tidak lebih terkenal!
Setelah melakukan penelitian ekstensif pada semua opsi di sekitar "enumerasi" di Scala, saya memposting tinjauan yang jauh lebih lengkap dari domain ini pada utas StackOverflow lainnya . Ini termasuk solusi untuk pola "seal trait + case object" di mana saya telah memecahkan masalah pemesanan inisialisasi kelas / objek JVM.
Di Scala sangat nyaman dengan https://github.com/lloydmeta/enumeratum
Project sangat bagus dengan contoh dan dokumentasi
Contoh ini hanya dari dokumen mereka harus membuat Anda tertarik
import enumeratum._
sealed trait Greeting extends EnumEntry
object Greeting extends Enum[Greeting] {
/*
`findValues` is a protected method that invokes a macro to find all `Greeting` object declarations inside an `Enum`
You use it to implement the `val values` member
*/
val values = findValues
case object Hello extends Greeting
case object GoodBye extends Greeting
case object Hi extends Greeting
case object Bye extends Greeting
}
// Object Greeting has a `withName(name: String)` method
Greeting.withName("Hello")
// => res0: Greeting = Hello
Greeting.withName("Haro")
// => java.lang.IllegalArgumentException: Haro is not a member of Enum (Hello, GoodBye, Hi, Bye)
// A safer alternative would be to use `withNameOption(name: String)` method which returns an Option[Greeting]
Greeting.withNameOption("Hello")
// => res1: Option[Greeting] = Some(Hello)
Greeting.withNameOption("Haro")
// => res2: Option[Greeting] = None
// It is also possible to use strings case insensitively
Greeting.withNameInsensitive("HeLLo")
// => res3: Greeting = Hello
Greeting.withNameInsensitiveOption("HeLLo")
// => res4: Option[Greeting] = Some(Hello)
// Uppercase-only strings may also be used
Greeting.withNameUppercaseOnly("HELLO")
// => res5: Greeting = Hello
Greeting.withNameUppercaseOnlyOption("HeLLo")
// => res6: Option[Greeting] = None
// Similarly, lowercase-only strings may also be used
Greeting.withNameLowercaseOnly("hello")
// => res7: Greeting = Hello
Greeting.withNameLowercaseOnlyOption("hello")
// => res8: Option[Greeting] = Some(Hello)