Scala ไม่มีประเภทที่ปลอดภัยenum
เหมือน Java ที่มี เมื่อพิจารณาจากค่าคงที่ที่เกี่ยวข้องชุดใดจะเป็นวิธีที่ดีที่สุดในสกาล่าในการเป็นตัวแทนค่าคงที่เหล่านั้น
Scala ไม่มีประเภทที่ปลอดภัยenum
เหมือน Java ที่มี เมื่อพิจารณาจากค่าคงที่ที่เกี่ยวข้องชุดใดจะเป็นวิธีที่ดีที่สุดในสกาล่าในการเป็นตัวแทนค่าคงที่เหล่านั้น
คำตอบ:
http://www.scala-lang.org/docu/files/api/scala/Enumeration.html
ตัวอย่างการใช้
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
}
ฉันต้องบอกว่าตัวอย่างที่คัดลอกจากเอกสาร Scalaโดยskaffmanด้านบนนั้นมีประโยชน์ จำกัด ในทางปฏิบัติ (คุณอาจใช้เช่นกันcase object
)
เพื่อให้ได้สิ่งที่ใกล้เคียงกับ Java มากที่สุดEnum
(เช่นมีเหตุผลtoString
และvalueOf
วิธีการ - บางทีคุณยังคงยืนยันค่า enum ในฐานข้อมูล) คุณจำเป็นต้องแก้ไขเล็กน้อย หากคุณใช้รหัสของskaffman :
WeekDay.valueOf("Sun") //returns None
WeekDay.Tue.toString //returns Weekday(2)
ในขณะที่ใช้การประกาศต่อไปนี้:
object WeekDay extends Enumeration {
type WeekDay = Value
val Mon = Value("Mon")
val Tue = Value("Tue")
... etc
}
คุณจะได้ผลลัพธ์ที่สมเหตุสมผลมากขึ้น:
WeekDay.valueOf("Sun") //returns Some(Sun)
WeekDay.Tue.toString //returns Tue
valueOf
คือwithName
ซึ่งไม่ส่งคืนตัวเลือกและส่ง NSE หากไม่มีการแข่งขัน อะไรกัน!
Map[Weekday.Weekday, Long]
และเพิ่มมูลค่าพูดMon
กับมันคอมไพเลอร์โยนข้อผิดพลาดประเภทที่ไม่ถูกต้อง คาดว่าจะเป็นวันธรรมดา ๆ ทุกวันพบว่าคุ้มค่า? ทำไมสิ่งนี้ถึงเกิดขึ้น
มีหลายวิธีในการทำ
1) ใช้สัญลักษณ์ มันจะไม่ให้ความปลอดภัยใด ๆ แก่คุณยกเว้นว่าจะไม่ยอมรับสัญลักษณ์ที่ไม่ใช่สัญลักษณ์ที่คาดว่าจะเป็น ฉันแค่พูดถึงที่นี่เพื่อความสมบูรณ์ นี่คือตัวอย่างการใช้งาน:
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) ใช้คลาสEnumeration
:
object Dimension extends Enumeration {
type Dimension = Value
val Row, Column = Value
}
หรือหากคุณต้องการทำให้เป็นอันดับหรือแสดง:
object Dimension extends Enumeration("Row", "Column") {
type Dimension = Value
val Row, Column = Value
}
สิ่งนี้สามารถใช้ดังนี้:
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 /
น่าเสียดายที่มันไม่ได้รับประกันว่าจะมีการแข่งขันทั้งหมด ถ้าฉันลืมใส่ Row หรือ Column ในการแข่งขันคอมไพเลอร์ Scala จะไม่เตือนฉัน ดังนั้นมันจึงทำให้ผมบางความปลอดภัยประเภท แต่ไม่มากเท่าที่จะได้รับ
3) วัตถุกรณี:
sealed abstract class Dimension
case object Row extends Dimension
case object Column extends Dimension
ตอนนี้ถ้าฉันออกจากกรณีในmatch
คอมไพเลอร์จะเตือนฉัน:
MatrixInt.scala:70: warning: match is not exhaustive!
missing combination Column
what match {
^
one warning found
มันใช้วิธีเดียวกันและไม่จำเป็นต้อง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 /
คุณอาจสงสัยว่าทำไมต้องใช้การแจงนับแทนที่จะเป็นวัตถุเคส ตามความเป็นจริงวัตถุเคสมีข้อดีหลายครั้งเช่นที่นี่ แม้ว่าคลาส Enumeration จะมีวิธีการรวบรวมมากมายเช่นองค์ประกอบ (ตัววนซ้ำบน Scala 2.8) ซึ่งจะส่งกลับค่า Iterator, แผนที่, flatMap, ตัวกรอง ฯลฯ
คำตอบนี้เป็นส่วนที่เลือกจากบทความนี้ในบล็อกของฉัน
Symbol
อินสแตนซ์ไม่สามารถมีช่องว่างหรืออักขระพิเศษได้ คนส่วนใหญ่เมื่อเผชิญหน้ากับSymbol
ชั้นเรียนครั้งแรกอาจคิดอย่างนั้น แต่จริง ๆ แล้วไม่ถูกต้อง Symbol("foo !% bar -* baz")
รวบรวมและทำงานได้อย่างสมบูรณ์แบบ กล่าวอีกนัยหนึ่งคุณสามารถสร้างSymbol
อินสแตนซ์ที่ล้อมรอบสตริงใด ๆ ได้อย่างสมบูรณ์(คุณไม่สามารถทำได้ด้วยการซินแท็กซ์น้ำตาลเดี่ยว "coma") สิ่งเดียวที่Symbol
รับประกันได้คือความเป็นเอกลักษณ์ของสัญลักษณ์ที่กำหนดทำให้การเปรียบเทียบและจับคู่ได้เร็วขึ้นเล็กน้อย
String
ยกตัวอย่างเช่นเป็นอาร์กิวเมนต์ของSymbol
พารามิเตอร์
String
ด้วยคลาสอื่นซึ่งโดยทั่วไปแล้วเป็น wrapper รอบ ๆ สตริงและสามารถแปลงได้อย่างอิสระในทั้งสองทิศทาง (ตามกรณีSymbol
) ฉันเดาว่านั่นคือสิ่งที่คุณหมายถึงเมื่อพูดว่า "มันจะไม่ให้ความปลอดภัยใด ๆ กับคุณ" แต่ก็ไม่ชัดเจนนักเนื่องจาก OP ได้ขอวิธีแก้ปัญหาอย่างปลอดภัยสำหรับประเภท ฉันไม่แน่ใจว่าในขณะที่เขียนคุณรู้ว่าไม่เพียง แต่มันจะไม่ปลอดภัยเพราะสิ่งเหล่านั้นไม่ได้ enums เลย แต่ยัง Symbol
ไม่รับประกันว่าอาร์กิวเมนต์ที่ผ่านจะไม่มีตัวอักษรพิเศษ
'foo
สัญกรณ์ซึ่งไม่ดักคอ สตริงที่ไม่ใช่ตัวระบุ) นี่คือความเข้าใจผิดที่ฉันต้องการปัดเป่าสำหรับผู้อ่านในอนาคตใด ๆ
วิธี verbose น้อยกว่าเล็กน้อยในการประกาศ enumerations ชื่อ:
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
แน่นอนปัญหาที่นี่คือคุณจะต้องเรียงลำดับของชื่อและ vals ให้ตรงกันซึ่งจะทำได้ง่ายขึ้นหากประกาศชื่อและ val ในบรรทัดเดียวกัน
คุณสามารถใช้คลาสนามธรรมที่ปิดผนึกแทนการแจงนับตัวอย่างเช่น:
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))
}
}
หลังจากทำวิจัยอย่างกว้างขวางเกี่ยวกับตัวเลือกทั้งหมดที่อยู่รอบ ๆ "enumerations" ใน Scala ผมโพสต์ภาพรวมที่สมบูรณ์มากขึ้นของโดเมนนี้อีกด้าย StackOverflow มันมีวิธีแก้ปัญหากับรูปแบบ "ลักษณะนิสัย + กรณีวัตถุ" ที่ฉันได้แก้ไขปัญหาการสั่งซื้อการเริ่มต้นคลาส / วัตถุ JVM
Dotty (Scala 3) จะมี enums รองรับอยู่ ตรวจสอบที่นี่และที่นี่
ใน Scala สะดวกสบายมากกับhttps://github.com/lloydmeta/enumeratum
โครงการดีมากพร้อมตัวอย่างและเอกสารประกอบ
เพียงแค่ตัวอย่างจากเอกสารของพวกเขาควรทำให้คุณสนใจ
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)