Scala không có loại an toàn enum
như Java có. Đưa ra một tập hợp các hằng số liên quan, cách tốt nhất trong Scala để đại diện cho các hằng số đó là gì?
Scala không có loại an toàn enum
như Java có. Đưa ra một tập hợp các hằng số liên quan, cách tốt nhất trong Scala để đại diện cho các hằng số đó là gì?
Câu trả lời:
http://www.scala-lang.org/docu/files/api/scala/Enumutions.html
Ví dụ sử dụng
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
}
Tôi phải nói rằng ví dụ được sao chép từ tài liệu Scala của skaffman ở trên là tiện ích hạn chế trong thực tế (bạn cũng có thể sử dụng case object
s).
Để có được một cái gì đó gần giống với Java nhất Enum
(nghĩa là với các phương thức toString
và valueOf
phương thức hợp lý - có lẽ bạn đang duy trì các giá trị enum cho cơ sở dữ liệu), bạn cần sửa đổi nó một chút. Nếu bạn đã sử dụng mã của skaffman :
WeekDay.valueOf("Sun") //returns None
WeekDay.Tue.toString //returns Weekday(2)
Trong khi sử dụng khai báo sau:
object WeekDay extends Enumeration {
type WeekDay = Value
val Mon = Value("Mon")
val Tue = Value("Tue")
... etc
}
Bạn nhận được kết quả hợp lý hơn:
WeekDay.valueOf("Sun") //returns Some(Sun)
WeekDay.Tue.toString //returns Tue
valueOf
thay thế của @macias là withName
, không trả lại Tùy chọn và ném NSE nếu không có kết quả khớp. Cái gì!
Map[Weekday.Weekday, Long]
và thêm một giá trị Mon
, trình biên dịch sẽ đưa ra một lỗi loại không hợp lệ. Ngày dự kiến. Ngày tìm thấy giá trị? Lý do tại sao điều này xảy ra?
Có nhiều cách làm.
1) Sử dụng các ký hiệu. Tuy nhiên, nó sẽ không cung cấp cho bạn bất kỳ loại an toàn nào, ngoài việc không chấp nhận các biểu tượng không phải là biểu tượng được mong đợi. Tôi chỉ đề cập đến nó ở đây cho đầy đủ. Đây là một ví dụ về việc sử dụng:
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) Sử dụng lớp Enumeration
:
object Dimension extends Enumeration {
type Dimension = Value
val Row, Column = Value
}
hoặc, nếu bạn cần nối tiếp hoặc hiển thị nó:
object Dimension extends Enumeration("Row", "Column") {
type Dimension = Value
val Row, Column = Value
}
Điều này có thể được sử dụng như thế này:
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 /
Thật không may, nó không đảm bảo rằng tất cả các trận đấu được tính. Nếu tôi quên đặt Row hoặc Cột trong trận đấu, trình biên dịch Scala sẽ không cảnh báo tôi. Vì vậy, nó mang lại cho tôi một số loại an toàn, nhưng không nhiều như có thể đạt được.
3) Đối tượng trường hợp:
sealed abstract class Dimension
case object Row extends Dimension
case object Column extends Dimension
Bây giờ, nếu tôi bỏ qua một trường hợp trên a match
, trình biên dịch sẽ cảnh báo tôi:
MatrixInt.scala:70: warning: match is not exhaustive!
missing combination Column
what match {
^
one warning found
Nó được sử dụng khá nhiều theo cùng một cách, và thậm chí không cần 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 /
Sau đó, bạn có thể tự hỏi tại sao lại sử dụng Bảng liệt kê thay vì các đối tượng trường hợp. Như một vấn đề của thực tế, các đối tượng trường hợp có lợi thế nhiều lần, chẳng hạn như ở đây. Mặc dù vậy, lớp Enumutions có nhiều phương thức Collection, chẳng hạn như các phần tử (iterator trên Scala 2.8), trả về một Iterator, map, FlatMap, bộ lọc, v.v.
Câu trả lời này về cơ bản là một phần được chọn từ bài viết này trong blog của tôi.
Symbol
thể hiện không thể có khoảng trắng hoặc ký tự đặc biệt. Hầu hết mọi người khi lần đầu tiên bắt gặp Symbol
lớp học có thể nghĩ như vậy, nhưng thực sự không chính xác. Symbol("foo !% bar -* baz")
biên dịch và chạy hoàn toàn tốt. Nói cách khác, bạn hoàn toàn có thể tạo ra các Symbol
trường hợp bao bọc bất kỳ chuỗi (bạn không thể thực hiện nó với đường cú pháp "hôn mê đơn"). Điều duy nhất Symbol
đảm bảo là tính duy nhất của bất kỳ biểu tượng nào, làm cho nó nhanh hơn một chút để so sánh và so sánh.
String
, ví dụ, làm đối số cho một Symbol
tham số.
String
bằng một lớp khác về cơ bản là một trình bao bọc xung quanh một chuỗi và có thể được chuyển đổi tự do theo cả hai hướng (như trường hợp Symbol
). Tôi đoán đó là những gì bạn muốn nói khi nói "Nó sẽ không mang lại cho bạn bất kỳ loại an toàn nào", điều đó không rõ ràng khi OP yêu cầu rõ ràng về các giải pháp an toàn loại. Tôi không chắc tại thời điểm viết bài bạn có biết rằng nó không chỉ không an toàn vì những thứ đó không phải là enum, mà còn Symbol
không đảm bảo rằng đối số được thông qua sẽ không có ký tự đặc biệt.
'foo
ký hiệu đặc biệt không loại trừ chuỗi không định danh). Đây là quan niệm sai lầm mà tôi muốn xua tan cho bất kỳ độc giả nào trong tương lai.
Một cách khai báo hơi dài dòng hơn một chút:
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
Tất nhiên vấn đề ở đây là bạn sẽ cần phải giữ trật tự của tên và vals đồng bộ, điều này dễ thực hiện hơn nếu tên và val được khai báo trên cùng một dòng.
Bạn có thể sử dụng một lớp trừu tượng kín thay vì liệt kê, ví dụ:
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))
}
}
Sau khi thực hiện nghiên cứu sâu rộng về tất cả các tùy chọn xung quanh "bảng liệt kê" trong Scala, tôi đã đăng một tổng quan đầy đủ hơn về miền này trên một luồng StackOverflow khác . Nó bao gồm một giải pháp cho mẫu "đối tượng đặc trưng + trường hợp được niêm phong" trong đó tôi đã giải quyết vấn đề sắp xếp khởi tạo lớp / đối tượng JVM.
Trong Scala, nó rất thoải mái với https://github.com/lloydmeta/enumeratum
Dự án thực sự tốt với các ví dụ và tài liệu
Chỉ ví dụ này từ tài liệu của họ sẽ khiến bạn quan tâm đến
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)