มีแนวทางปฏิบัติที่ดีที่สุดเกี่ยวกับเวลาใดที่จะใช้คลาสเคส (หรืออ็อบเจ็กต์เคส) เทียบกับการขยายการแจงนับใน Scala?
พวกเขาดูเหมือนจะให้ประโยชน์เหมือนกัน
enum
(สำหรับกลางปี 2563)
มีแนวทางปฏิบัติที่ดีที่สุดเกี่ยวกับเวลาใดที่จะใช้คลาสเคส (หรืออ็อบเจ็กต์เคส) เทียบกับการขยายการแจงนับใน Scala?
พวกเขาดูเหมือนจะให้ประโยชน์เหมือนกัน
enum
(สำหรับกลางปี 2563)
คำตอบ:
ความแตกต่างที่สำคัญอย่างหนึ่งคือEnumeration
มาพร้อมกับการสนับสนุนการยกตัวอย่างจากname
สตริง ตัวอย่างเช่น:
object Currency extends Enumeration {
val GBP = Value("GBP")
val EUR = Value("EUR") //etc.
}
จากนั้นคุณสามารถทำได้:
val ccy = Currency.withName("EUR")
สิ่งนี้มีประโยชน์เมื่อต้องการยืนยันการแจกแจง (เช่นไปยังฐานข้อมูล) หรือสร้างจากข้อมูลที่อยู่ในไฟล์ อย่างไรก็ตามฉันพบโดยทั่วไปว่าการแจกแจงนั้นค่อนข้างงุ่มง่ามใน Scala และมีความรู้สึกของ add-on ที่น่าอึดอัดใจดังนั้นตอนนี้ฉันมักจะใช้case object
s A case object
มีความยืดหยุ่นมากกว่า Enum:
sealed trait Currency { def name: String }
case object EUR extends Currency { val name = "EUR" } //etc.
case class UnknownCurrency(name: String) extends Currency
ดังนั้นตอนนี้ฉันได้เปรียบ ...
trade.ccy match {
case EUR =>
case UnknownCurrency(code) =>
}
ในฐานะที่เป็น@ chaotic3quilibriumชี้ให้เห็น (มีการแก้ไขบางอย่างเพื่อความสะดวกในการอ่าน):
เกี่ยวกับรูปแบบ "UnknownCurrency (รหัส)" มีวิธีอื่นในการจัดการไม่ค้นหาสตริงรหัสสกุลเงินมากกว่า "ทำลาย" ลักษณะชุดที่ปิดของ
Currency
ประเภทUnknownCurrency
การเป็นประเภทCurrency
สามารถแอบเข้าไปในส่วนอื่น ๆ ของ API ได้แล้วขอแนะนำให้ผลักกรณีนี้ออกไปข้างนอก
Enumeration
และทำให้ลูกค้าจัดการกับOption[Currency]
ประเภทที่ชัดเจนว่ามีปัญหาการจับคู่และ "กระตุ้น" ผู้ใช้ API ให้เรียงลำดับตัวเขาเอง
หากต้องการติดตามคำตอบอื่น ๆ ที่นี่ข้อเสียเปรียบหลักของcase object
s เกินEnumeration
คือ:
ไม่สามารถย้ำกว่าทุกกรณีของ "การแจงนับ" เป็นกรณีนี้ แต่ฉันพบว่ามันยากมากในทางปฏิบัติที่จำเป็น
ไม่สามารถยกตัวอย่างได้อย่างง่ายดายจากค่าที่เก็บไว้ สิ่งนี้ก็เป็นจริงเช่นกันยกเว้นในกรณีที่มีการแจกแจงจำนวนมาก (ตัวอย่างเช่นทุกสกุลเงิน) สิ่งนี้ไม่ได้มีค่าใช้จ่ายมาก
trade.ccy
ในลักษณะตัวอย่างที่ปิดสนิท
case
object
สร้างรอยเท้าโค้ดขนาดใหญ่กว่า (~ 4x) มากกว่าEnumeration
? ความแตกต่างที่มีประโยชน์โดยเฉพาะอย่างยิ่งสำหรับscala.js
โครงการที่ต้องการรอยขนาดเล็ก
อัปเดต:มีการสร้างโซลูชันที่ใช้มาโคร ใหม่ซึ่งเหนือกว่าโซลูชันที่ฉันเขียนไว้ด้านล่าง ผมขอแนะนำให้ใช้ใหม่นี้การแก้ปัญหาตามมาโคร และดูเหมือนว่าแผนสำหรับ Dotty จะทำให้รูปแบบการแก้ปัญหา Enum นี้เป็นส่วนหนึ่งของภาษา Whoohoo!
สรุป:
มีสามรูปแบบพื้นฐานสำหรับการพยายามสร้าง Java Enum
ภายในโครงการ Scala สองในสามรูปแบบ; การใช้ Java โดยตรงEnum
และscala.Enumeration
ไม่สามารถเปิดใช้งานการจับคู่รูปแบบที่ครบถ้วนสมบูรณ์ของ Scala ได้ และอันที่สาม "ผนึกลักษณะ + กรณีวัตถุ" ไม่ ... แต่มีความซับซ้อนของการเริ่มต้นคลาส / วัตถุ JVMส่งผลให้เกิดการสร้างดัชนีลำดับไม่สอดคล้องกัน
ฉันสร้างโซลูชันที่มีสองคลาส การแจงนับและการแจงนับตกแต่งอยู่ในส่วนสำคัญนี้ ฉันไม่ได้โพสต์รหัสลงในกระทู้นี้เนื่องจากไฟล์สำหรับการแจงนับค่อนข้างใหญ่ (+400 บรรทัด - มีความคิดเห็นมากมายที่อธิบายถึงบริบทการใช้งาน)
รายละเอียด:
คำถามที่คุณถามนั้นค่อนข้างธรรมดา "... เมื่อใดควรใช้case
คลาสobjects
กับการขยาย[scala.]Enumeration
" และปรากฎว่ามีคำตอบที่เป็นไปได้มากมายแต่ละคำตอบขึ้นอยู่กับรายละเอียดปลีกย่อยของข้อกำหนดโครงการเฉพาะที่คุณมี คำตอบสามารถลดลงได้ถึงสามรูปแบบพื้นฐาน
ในการเริ่มต้นให้ตรวจสอบให้แน่ใจว่าเรากำลังทำงานจากแนวคิดพื้นฐานเดียวกันว่าการแจงนับคืออะไร ลองกำหนดการแจงนับเป็นส่วนใหญ่ในแง่ของการEnum
ให้เป็นของ Java 5 (1.5) :
Enum
แล้วละก็คงเป็นการดีที่จะได้ใช้ประโยชน์จากรูปแบบการจับคู่แบบหมดจดของ Scala เพื่อตรวจสอบการแจงนับ ต่อไปเรามาดูรูปแบบการแก้ปัญหาที่พบบ่อยทั้งสามแบบที่โพสต์:
A)จริงๆแล้วใช้รูปแบบJavaEnum
โดยตรง(ในโครงการ Scala / Java แบบผสม):
public enum ChessPiece {
KING('K', 0)
, QUEEN('Q', 9)
, BISHOP('B', 3)
, KNIGHT('N', 3)
, ROOK('R', 5)
, PAWN('P', 1)
;
private char character;
private int pointValue;
private ChessPiece(char character, int pointValue) {
this.character = character;
this.pointValue = pointValue;
}
public int getCharacter() {
return character;
}
public int getPointValue() {
return pointValue;
}
}
ไอเท็มต่อไปนี้จากนิยามการแจงนับไม่พร้อมใช้งาน:
สำหรับโครงการปัจจุบันของฉันฉันไม่ได้รับประโยชน์จากการรับความเสี่ยงรอบทางเดินของโครงการผสม Scala / Java และถึงแม้ว่าฉันสามารถเลือกที่จะทำโครงการที่หลากหลายรายการที่ 7 เป็นสิ่งสำคัญสำหรับฉันที่จะรวบรวมปัญหาเวลารวบรวมเมื่อ / เมื่อฉันเพิ่ม / ลบสมาชิกการแจงนับหรือกำลังเขียนรหัสใหม่เพื่อจัดการกับสมาชิกการแจงนับที่มีอยู่
B)ใช้รูปแบบ " sealed trait
+case objects
":
sealed trait ChessPiece {def character: Char; def pointValue: Int}
object ChessPiece {
case object KING extends ChessPiece {val character = 'K'; val pointValue = 0}
case object QUEEN extends ChessPiece {val character = 'Q'; val pointValue = 9}
case object BISHOP extends ChessPiece {val character = 'B'; val pointValue = 3}
case object KNIGHT extends ChessPiece {val character = 'N'; val pointValue = 3}
case object ROOK extends ChessPiece {val character = 'R'; val pointValue = 5}
case object PAWN extends ChessPiece {val character = 'P'; val pointValue = 1}
}
ไอเท็มต่อไปนี้จากนิยามการแจงนับไม่พร้อมใช้งาน:
สามารถพิสูจน์ได้ว่าเป็นไปตามรายการคำนิยามการแจกแจง 5 และ 6 สำหรับ 5 เป็นการยืดเวลาในการอ้างสิทธิ์ได้อย่างมีประสิทธิภาพ สำหรับ 6 ไม่ใช่เรื่องง่ายที่จะขยายเพื่อเก็บข้อมูล singleton-ness เพิ่มเติมที่เกี่ยวข้อง
C)การใช้scala.Enumeration
รูปแบบ (ได้รับแรงบันดาลใจจากคำตอบ StackOverflow ):
object ChessPiece extends Enumeration {
val KING = ChessPieceVal('K', 0)
val QUEEN = ChessPieceVal('Q', 9)
val BISHOP = ChessPieceVal('B', 3)
val KNIGHT = ChessPieceVal('N', 3)
val ROOK = ChessPieceVal('R', 5)
val PAWN = ChessPieceVal('P', 1)
protected case class ChessPieceVal(character: Char, pointValue: Int) extends super.Val()
implicit def convert(value: Value) = value.asInstanceOf[ChessPieceVal]
}
ไอเท็มต่อไปนี้จากนิยามการแจงนับไม่พร้อมใช้งาน (เกิดขึ้นเหมือนกับรายการสำหรับการใช้ Java Enum โดยตรง):
อีกครั้งสำหรับโครงการปัจจุบันของฉันรายการที่ 7 เป็นสิ่งสำคัญที่ทำให้ฉันสามารถรวบรวมปัญหาเวลารวบรวมหาก / เมื่อฉันเพิ่ม / ลบสมาชิกการแจงนับหรือกำลังเขียนรหัสใหม่เพื่อจัดการกับสมาชิกการแจงนับที่มีอยู่
ดังนั้นตามคำจำกัดความด้านบนของการแจงนับไม่มีวิธีแก้ปัญหาสามข้อใดทำงานได้เนื่องจากไม่ได้ให้ทุกสิ่งที่ระบุไว้ในคำนิยามการแจงนับด้านบน:
ในที่สุดโซลูชันเหล่านี้แต่ละเครื่องสามารถนำกลับมาทำใหม่ / ขยาย / ปรับโครงสร้างใหม่เพื่อพยายามครอบคลุมความต้องการที่ขาดหายไปของแต่ละคน อย่างไรก็ตามทั้ง Java Enum
และscala.Enumeration
โซลูชันไม่สามารถขยายได้อย่างเพียงพอเพื่อให้รายการที่ 7 และสำหรับโครงการของฉันเองนี่เป็นหนึ่งในค่าที่น่าสนใจของการใช้แบบปิดใน Scala ฉันขอแนะนำให้รวบรวมคำเตือน / ข้อผิดพลาดในการรวบรวมเวลาเพื่อระบุว่าฉันมีช่องว่าง / ปัญหาในรหัสของฉันซึ่งต่างจากการรวบรวมจากข้อยกเว้น / ความล้มเหลวรันไทม์สำหรับการผลิต
ในเรื่องนั้นฉันตั้งใจจะทำงานร่วมกับcase object
ทางเดินเพื่อดูว่าฉันจะสามารถสร้างโซลูชันที่ครอบคลุมคำจำกัดความการแจงนับทั้งหมดข้างต้นได้หรือไม่ ความท้าทายแรกคือการผลักดันผ่านแกนหลักของปัญหาการเริ่มต้นคลาส / วัตถุ JVM (ครอบคลุมรายละเอียดในโพสต์ StackOverflow นี้ ) และในที่สุดฉันก็สามารถหาคำตอบได้
ในฐานะที่เป็นวิธีการแก้ปัญหาของฉันเป็นสองลักษณะ; EnumerationและEnumerationDecoratedและเนื่องจากEnumeration
คุณลักษณะมีความยาวมากกว่า 400 บรรทัด (ความคิดเห็นจำนวนมากที่อธิบายบริบท) ฉันกำลังดำเนินการวางลงในเธรดนี้ สำหรับรายละเอียดโปรดกระโดดโดยตรงกับการสรุปสาระสำคัญ
นี่คือสิ่งที่แก้ปัญหาลงท้ายมองเช่นการใช้ความคิดข้อมูลเดียวกันกับข้างต้น (อย่างเต็มที่แสดงความคิดเห็นรุ่นสามารถใช้ได้ที่นี่ ) EnumerationDecorated
และดำเนินการใน
import scala.reflect.runtime.universe.{TypeTag,typeTag}
import org.public_domain.scala.utils.EnumerationDecorated
object ChessPiecesEnhancedDecorated extends EnumerationDecorated {
case object KING extends Member
case object QUEEN extends Member
case object BISHOP extends Member
case object KNIGHT extends Member
case object ROOK extends Member
case object PAWN extends Member
val decorationOrderedSet: List[Decoration] =
List(
Decoration(KING, 'K', 0)
, Decoration(QUEEN, 'Q', 9)
, Decoration(BISHOP, 'B', 3)
, Decoration(KNIGHT, 'N', 3)
, Decoration(ROOK, 'R', 5)
, Decoration(PAWN, 'P', 1)
)
final case class Decoration private[ChessPiecesEnhancedDecorated] (member: Member, char: Char, pointValue: Int) extends DecorationBase {
val description: String = member.name.toLowerCase.capitalize
}
override def typeTagMember: TypeTag[_] = typeTag[Member]
sealed trait Member extends MemberDecorated
}
นี่คือตัวอย่างการใช้งานของคู่การแจงนับคุณลักษณะใหม่ที่ฉันสร้าง (อยู่ในGist นี้ ) เพื่อใช้ความสามารถทั้งหมดที่ต้องการและสรุปไว้ในคำจำกัดความการแจงนับ
สิ่งที่กังวลอย่างหนึ่งคือชื่อสมาชิกต้องระบุซ้ำ ( decorationOrderedSet
ในตัวอย่างด้านบน) ในขณะที่ฉันย่อให้เหลือน้อยที่สุดเพื่อทำซ้ำฉันไม่สามารถดูได้ว่าจะทำให้น้อยลงเนื่องจากปัญหาสองประการ:
getClass.getDeclaredClasses
มีลำดับที่ไม่ได้กำหนด (และค่อนข้างไม่น่าจะอยู่ในลำดับเดียวกันกับการcase object
ประกาศในซอร์สโค้ด)เมื่อพิจารณาถึงปัญหาทั้งสองนี้ฉันต้องพยายามพยายามสร้างคำสั่งโดยนัยและจำเป็นต้องให้ลูกค้ากำหนดและประกาศอย่างชัดเจนด้วยแนวคิดเกี่ยวกับชุดคำสั่งบางประเภท เนื่องจากคอลเลกชัน Scala ไม่มีการใช้ชุดคำสั่งแทรกสิ่งที่ดีที่สุดที่ฉันทำได้คือใช้List
และจากนั้นตรวจสอบรันไทม์ว่าเป็นชุดอย่างแท้จริง ไม่ใช่วิธีที่ฉันต้องการที่จะได้รับสิ่งนี้
และได้รับการออกแบบที่จำเป็นนี้รายการที่สอง / ชุดการสั่งซื้อval
ที่ได้รับChessPiecesEnhancedDecorated
ตัวอย่างข้างต้นมันเป็นไปได้ที่จะเพิ่มcase object PAWN2 extends Member
แล้วลืมที่จะเพิ่มการDecoration(PAWN2,'P2', 2)
decorationOrderedSet
จึงมีการตรวจสอบรันไทม์เพื่อตรวจสอบว่ารายการไม่ได้เป็นเพียงชุด sealed trait Member
แต่มีทั้งหมดของกรณีที่วัตถุที่ขยาย นั่นเป็นรูปแบบพิเศษของการสะท้อน / มาโครนรกในการทำงาน
กรุณาแสดงความคิดเห็นและ / หรือข้อเสนอแนะเกี่ยวกับการสรุปสาระสำคัญ
org.scalaolio.util.Enumeration
และorg.scalaolio.util.EnumerationDecorated
: scalaolio.org
อ็อบเจ็กต์เคสส่งคืนชื่อของพวกเขาสำหรับเมธอด toString ของพวกเขาดังนั้นการส่งผ่านในแยกต่างหากจึงไม่จำเป็น นี่คือรุ่นที่คล้ายกับของ jho (วิธีการอำนวยความสะดวกที่ละเว้นเพื่อความกะทัดรัด):
trait Enum[A] {
trait Value { self: A => }
val values: List[A]
}
sealed trait Currency extends Currency.Value
object Currency extends Enum[Currency] {
case object EUR extends Currency
case object GBP extends Currency
val values = List(EUR, GBP)
}
วัตถุขี้เกียจ โดยใช้ vals แทนเราสามารถวางรายการ แต่ต้องทำซ้ำชื่อ:
trait Enum[A <: {def name: String}] {
trait Value { self: A =>
_values :+= this
}
private var _values = List.empty[A]
def values = _values
}
sealed abstract class Currency(val name: String) extends Currency.Value
object Currency extends Enum[Currency] {
val EUR = new Currency("EUR") {}
val GBP = new Currency("GBP") {}
}
หากคุณไม่รังเกียจการโกงคุณสามารถโหลดค่าการแจงนับล่วงหน้าได้โดยใช้ API การสะท้อนกลับหรือบางอย่างเช่น Google Reflections วัตถุเคสที่ไม่ใช่สันหลังยาวให้ไวยากรณ์ที่สะอาดที่สุดแก่คุณ:
trait Enum[A] {
trait Value { self: A =>
_values :+= this
}
private var _values = List.empty[A]
def values = _values
}
sealed trait Currency extends Currency.Value
object Currency extends Enum[Currency] {
case object EUR extends Currency
case object GBP extends Currency
}
ดีและสะอาดพร้อมข้อดีทั้งหมดของคลาสเคสและการระบุ Java ส่วนตัวฉันกำหนดค่าการแจงนับนอกวัตถุเพื่อให้ตรงกับรหัสสกาล่าสำนวน:
object Currency extends Enum[Currency]
sealed trait Currency extends Currency.Value
case object EUR extends Currency
case object GBP extends Currency
Currency.values
ฉันจะรับค่ากลับที่ฉันได้เข้าถึงก่อนหน้านี้เท่านั้น มีวิธีใดบ้างไหม?
ข้อดีของการใช้คลาสเคสแทน Enumerations คือ:
ข้อดีของการใช้ Enumerations แทนคลาสของเคสคือ:
โดยทั่วไปแล้วถ้าคุณต้องการรายการค่าคงที่แบบง่ายตามชื่อให้ใช้การแจกแจง มิฉะนั้นถ้าคุณต้องการบางสิ่งที่ซับซ้อนกว่านี้เล็กน้อยหรือต้องการความปลอดภัยเพิ่มเติมของคอมไพเลอร์ที่บอกคุณว่าคุณมีการแข่งขันทั้งหมดที่ระบุไว้ให้ใช้คลาสเคส
UPDATE: โค้ดข้างล่างนี้มีข้อผิดพลาดที่อธิบายไว้ที่นี่ โปรแกรมทดสอบด้านล่างใช้งานได้ แต่ถ้าคุณใช้ DayOfWeek.Mon (ตัวอย่าง) ก่อน DayOfWeek เองมันจะล้มเหลวเพราะ DayOfWeek ยังไม่ได้เริ่มต้น (การใช้วัตถุภายในไม่ทำให้วัตถุภายนอกถูกเริ่มต้น) คุณยังคงสามารถใช้รหัสนี้ได้หากคุณทำอะไรบางอย่างval enums = Seq( DayOfWeek )
ในคลาสหลักบังคับให้เริ่มการทำงานของ enums หรือคุณสามารถใช้การดัดแปลงของ chaotic3quilibrium รอคอยที่จะ enum ตามแมโคร!
ถ้าคุณต้องการ
ดังนั้นสิ่งต่อไปนี้อาจเป็นที่สนใจ ข้อเสนอแนะยินดีต้อนรับ
ในการใช้งานนี้มีคลาสฐาน Enum และ EnumVal ที่เป็นนามธรรมซึ่งคุณขยาย เราจะเห็นชั้นเรียนเหล่านั้นในนาที แต่ก่อนอื่นนี่คือวิธีที่คุณจะกำหนด enum:
object DayOfWeek extends Enum {
sealed abstract class Val extends EnumVal
case object Mon extends Val; Mon()
case object Tue extends Val; Tue()
case object Wed extends Val; Wed()
case object Thu extends Val; Thu()
case object Fri extends Val; Fri()
case object Sat extends Val; Sat()
case object Sun extends Val; Sun()
}
โปรดทราบว่าคุณต้องใช้แต่ละค่า enum (เรียกใช้วิธีการใช้) เพื่อทำให้มันมีชีวิต [ฉันหวังว่าวัตถุภายในจะไม่ขี้เกียจเว้นแต่ฉันจะขอให้พวกมันเป็นพิเศษ ฉันคิด.]
แน่นอนว่าเราสามารถเพิ่มวิธีการ / ข้อมูลไปยัง DayOfWeek, Val หรือวัตถุเคสแต่ละรายการหากเราต้องการ
และนี่คือวิธีที่คุณจะใช้ Enum เช่นนี้:
object DayOfWeekTest extends App {
// To get a map from Int id to enum:
println( DayOfWeek.valuesById )
// To get a map from String name to enum:
println( DayOfWeek.valuesByName )
// To iterate through a list of the enum values in definition order,
// which can be made different from ID order, and get their IDs and names:
DayOfWeek.values foreach { v => println( v.id + " = " + v ) }
// To sort by ID or name:
println( DayOfWeek.values.sorted mkString ", " )
println( DayOfWeek.values.sortBy(_.toString) mkString ", " )
// To look up enum values by name:
println( DayOfWeek("Tue") ) // Some[DayOfWeek.Val]
println( DayOfWeek("Xyz") ) // None
// To look up enum values by id:
println( DayOfWeek(3) ) // Some[DayOfWeek.Val]
println( DayOfWeek(9) ) // None
import DayOfWeek._
// To compare enums as ordinals:
println( Tue < Fri )
// Warnings about non-exhaustive pattern matches:
def aufDeutsch( day: DayOfWeek.Val ) = day match {
case Mon => "Montag"
case Tue => "Dienstag"
case Wed => "Mittwoch"
case Thu => "Donnerstag"
case Fri => "Freitag"
// Commenting these out causes compiler warning: "match is not exhaustive!"
// case Sat => "Samstag"
// case Sun => "Sonntag"
}
}
นี่คือสิ่งที่คุณจะได้รับเมื่อรวบรวม:
DayOfWeekTest.scala:31: warning: match is not exhaustive!
missing combination Sat
missing combination Sun
def aufDeutsch( day: DayOfWeek.Val ) = day match {
^
one warning found
คุณสามารถแทนที่ "การจับคู่วัน" ด้วยการจับคู่ "(วัน: @unchecked)" ซึ่งคุณไม่ต้องการคำเตือนดังกล่าวหรือเพียงแค่รวมกรณีจับทั้งหมดในตอนท้าย
เมื่อคุณเรียกใช้โปรแกรมข้างต้นคุณจะได้รับผลลัพธ์นี้:
Map(0 -> Mon, 5 -> Sat, 1 -> Tue, 6 -> Sun, 2 -> Wed, 3 -> Thu, 4 -> Fri)
Map(Thu -> Thu, Sat -> Sat, Tue -> Tue, Sun -> Sun, Mon -> Mon, Wed -> Wed, Fri -> Fri)
0 = Mon
1 = Tue
2 = Wed
3 = Thu
4 = Fri
5 = Sat
6 = Sun
Mon, Tue, Wed, Thu, Fri, Sat, Sun
Fri, Mon, Sat, Sun, Thu, Tue, Wed
Some(Tue)
None
Some(Thu)
None
true
โปรดทราบว่าเนื่องจากรายการและแผนที่ไม่เปลี่ยนรูปคุณจึงสามารถลบองค์ประกอบต่างๆเพื่อสร้างชุดย่อยได้อย่างง่ายดายโดยไม่ทำลายตัวเอง
นี่คือคลาส Enum (และ EnumVal ภายใน)
abstract class Enum {
type Val <: EnumVal
protected var nextId: Int = 0
private var values_ = List[Val]()
private var valuesById_ = Map[Int ,Val]()
private var valuesByName_ = Map[String,Val]()
def values = values_
def valuesById = valuesById_
def valuesByName = valuesByName_
def apply( id : Int ) = valuesById .get(id ) // Some|None
def apply( name: String ) = valuesByName.get(name) // Some|None
// Base class for enum values; it registers the value with the Enum.
protected abstract class EnumVal extends Ordered[Val] {
val theVal = this.asInstanceOf[Val] // only extend EnumVal to Val
val id = nextId
def bumpId { nextId += 1 }
def compare( that:Val ) = this.id - that.id
def apply() {
if ( valuesById_.get(id) != None )
throw new Exception( "cannot init " + this + " enum value twice" )
bumpId
values_ ++= List(theVal)
valuesById_ += ( id -> theVal )
valuesByName_ += ( toString -> theVal )
}
}
}
และนี่คือการใช้งานขั้นสูงเพิ่มเติมซึ่งควบคุม ID และเพิ่มข้อมูล / วิธีการในการนามธรรม Val และเพื่อ enum เอง:
object DayOfWeek extends Enum {
sealed abstract class Val( val isWeekday:Boolean = true ) extends EnumVal {
def isWeekend = !isWeekday
val abbrev = toString take 3
}
case object Monday extends Val; Monday()
case object Tuesday extends Val; Tuesday()
case object Wednesday extends Val; Wednesday()
case object Thursday extends Val; Thursday()
case object Friday extends Val; Friday()
nextId = -2
case object Saturday extends Val(false); Saturday()
case object Sunday extends Val(false); Sunday()
val (weekDays,weekendDays) = values partition (_.isWeekday)
}
var
] เป็นบาปของมนุษย์ที่มีแนวเขตแดนในโลก FP" - ฉันไม่คิดว่าความคิดเห็นนั้นเป็นที่ยอมรับในระดับสากล
ฉันมี lib ง่าย ๆ ที่นี่ที่ให้คุณใช้ลักษณะ / คลาสที่ปิดผนึกเป็นค่า enum โดยไม่ต้องเก็บรักษารายการค่าของคุณเอง knownDirectSubclasses
มันอาศัยแมโครง่ายๆที่ไม่ขึ้นอยู่กับรถ
อัปเดตมีนาคม 2560: ตามความเห็นของAnthony Acciolyการscala.Enumeration/enum
ประชาสัมพันธ์ถูกปิด
บ้า ๆ บอ ๆ (คอมไพเลอร์รุ่นต่อไปสำหรับ Scala) จะนำ แต่ขี้หลงขี้ลืมปัญหา 1970และมาร์ตินโอเดอร์ สกี ของ PR 1958
หมายเหตุ: ขณะนี้มีข้อเสนอที่จะลบออกscala.Enumeration
: (สิงหาคม 2559, 6 ปีขึ้นไป) : PR 5352
เลิกใช้
scala.Enumeration
เพิ่ม@enum
คำอธิบายประกอบไวยากรณ์
@enum
class Toggle {
ON
OFF
}
เป็นตัวอย่างการนำไปปฏิบัติที่เป็นไปได้ความตั้งใจที่จะสนับสนุน ADT ที่สอดคล้องกับข้อ จำกัด บางประการ (ไม่มีการซ้อนการเรียกซ้ำหรือการสร้างพารามิเตอร์คอนสตรัคเตอร์ที่แตกต่างกัน) เช่น:
@enum
sealed trait Toggle
case object ON extends Toggle
case object OFF extends Toggle
deprecates
scala.Enumeration
ภัยพิบัติดักดานที่เป็นข้อดีของ @enum มากกว่าเรื่องกาล่า
- ใช้งานได้จริง
- Java interop
- ไม่มีปัญหาการลบ
- ไม่มีสับสน mini-DSL เพื่อเรียนรู้เมื่อกำหนด enumerations
ข้อเสีย: ไม่มี
สิ่งนี้แก้ปัญหาการไม่สามารถมีหนึ่ง codebase ที่รองรับ Scala-JVM
Scala.js
และ Scala-Native (ไม่สนับสนุนซอร์สโค้ด Java บนScala.js/Scala-Native
ซอร์สโค้ดของ Scala ไม่สามารถกำหนด enums ที่ APIs ที่มีอยู่บน Scala-JVM ได้รับการยอมรับ
ข้อเสียอีกอย่างหนึ่งของคลาสเคสเทียบกับการแจกแจงเมื่อคุณต้องทำซ้ำหรือกรองในทุกอินสแตนซ์ นี่เป็นความสามารถในตัวของ Enumeration (และ Java enums ด้วย) ในขณะที่คลาสเคสไม่สนับสนุนความสามารถดังกล่าวโดยอัตโนมัติ
กล่าวอีกนัยหนึ่ง: "ไม่มีวิธีง่ายๆในการรับรายการชุดค่ารวมทั้งหมดพร้อมคลาสเคส"
หากคุณจริงจังเกี่ยวกับการรักษาความสามารถในการทำงานร่วมกันกับภาษา JVM อื่น ๆ (เช่น Java) ตัวเลือกที่ดีที่สุดคือการเขียน enums Java งานเหล่านั้นโปร่งใสจากทั้ง Scala และโค้ด Java ซึ่งมากกว่าที่สามารถพูดได้สำหรับscala.Enumeration
วัตถุหรือกรณี อย่ามีห้องสมุดแจกแจงใหม่สำหรับโครงการงานอดิเรกใหม่ทุกรายการใน GitHub หากสามารถหลีกเลี่ยงได้!
ฉันเคยเห็นรุ่นต่าง ๆ ของการทำคลาสเคสเลียนแบบการแจงนับ นี่คือรุ่นของฉัน:
trait CaseEnumValue {
def name:String
}
trait CaseEnum {
type V <: CaseEnumValue
def values:List[V]
def unapply(name:String):Option[String] = {
if (values.exists(_.name == name)) Some(name) else None
}
def unapply(value:V):String = {
return value.name
}
def apply(name:String):Option[V] = {
values.find(_.name == name)
}
}
ซึ่งอนุญาตให้คุณสร้างคลาสเคสที่มีลักษณะดังต่อไปนี้:
abstract class Currency(override name:String) extends CaseEnumValue {
}
object Currency extends CaseEnum {
type V = Site
case object EUR extends Currency("EUR")
case object GBP extends Currency("GBP")
var values = List(EUR, GBP)
}
บางทีบางคนอาจมีกลอุบายที่ดีกว่าการเพิ่มคลาสเคสแต่ละรายการลงในรายการอย่างที่ฉันทำ นี่คือทั้งหมดที่ฉันสามารถทำได้ในเวลานั้น
ฉันได้ย้อนกลับไปทั้งสองทางเลือกสองสามครั้งสุดท้ายที่ฉันต้องการ จนกระทั่งเมื่อเร็ว ๆ นี้การตั้งค่าของฉันมีไว้สำหรับตัวเลือกคุณลักษณะ / ตัวเลือกวัตถุที่ปิดผนึก
1) ปฏิญญาการแจงนับสกาล่า
object OutboundMarketMakerEntryPointType extends Enumeration {
type OutboundMarketMakerEntryPointType = Value
val Alpha, Beta = Value
}
2) คุณสมบัติปิดผนึก + กรณีวัตถุ
sealed trait OutboundMarketMakerEntryPointType
case object AlphaEntryPoint extends OutboundMarketMakerEntryPointType
case object BetaEntryPoint extends OutboundMarketMakerEntryPointType
แม้ว่าสิ่งเหล่านี้จะไม่ตรงกับสิ่งที่แจงนับให้จริงทั้งหมด แต่ด้านล่างเป็นข้อดีและข้อเสีย:
การนับสกาล่า
ข้อดี: - ฟังก์ชันสำหรับการสร้างอินสแตนซ์ด้วยตัวเลือกหรือสมมติว่าถูกต้องโดยตรง (ง่ายขึ้นเมื่อโหลดจากที่จัดเก็บแบบต่อเนื่อง) - สนับสนุนการทำซ้ำค่าที่เป็นไปได้ทั้งหมด
ข้อด้อย: - ไม่สนับสนุนคำเตือนการรวบรวมสำหรับการค้นหาที่ไม่ละเอียด (ทำให้การจับคู่รูปแบบเหมาะสมน้อยกว่า)
กรณีวัตถุ / ลักษณะที่ปิดผนึก
ข้อดี: - ด้วยการใช้คุณสมบัติที่ปิดสนิทเราสามารถสร้างอินสแตนซ์ของค่าล่วงหน้าบางค่าได้ขณะที่ค่าอื่นสามารถถูกฉีดได้ในเวลาการสร้าง - รองรับการจับคู่รูปแบบเต็มรูปแบบ
ข้อด้อย: - ตรวจสอบจากร้านค้าแบบถาวร - คุณมักจะต้องใช้การจับคู่รูปแบบที่นี่หรือกำหนดรายการของคุณเองที่เป็นไปได้ทั้งหมด 'ค่า enum'
สิ่งที่ทำให้ฉันเปลี่ยนความคิดเห็นในท้ายที่สุดคือสิ่งที่ตัวอย่างต่อไปนี้:
object DbInstrumentQueries {
def instrumentExtractor(tableAlias: String = "s")(rs: ResultSet): Instrument = {
val symbol = rs.getString(tableAlias + ".name")
val quoteCurrency = rs.getString(tableAlias + ".quote_currency")
val fixRepresentation = rs.getString(tableAlias + ".fix_representation")
val pointsValue = rs.getInt(tableAlias + ".points_value")
val instrumentType = InstrumentType.fromString(rs.getString(tableAlias +".instrument_type"))
val productType = ProductType.fromString(rs.getString(tableAlias + ".product_type"))
Instrument(symbol, fixRepresentation, quoteCurrency, pointsValue, instrumentType, productType)
}
}
object InstrumentType {
def fromString(instrumentType: String): InstrumentType = Seq(CurrencyPair, Metal, CFD)
.find(_.toString == instrumentType).get
}
object ProductType {
def fromString(productType: String): ProductType = Seq(Commodity, Currency, Index)
.find(_.toString == productType).get
}
การ.get
โทรนั้นน่าเกลียด - ใช้การแจงนับแทนฉันสามารถเรียกเมธอด withName บนการแจงนับดังนี้:
object DbInstrumentQueries {
def instrumentExtractor(tableAlias: String = "s")(rs: ResultSet): Instrument = {
val symbol = rs.getString(tableAlias + ".name")
val quoteCurrency = rs.getString(tableAlias + ".quote_currency")
val fixRepresentation = rs.getString(tableAlias + ".fix_representation")
val pointsValue = rs.getInt(tableAlias + ".points_value")
val instrumentType = InstrumentType.withNameString(rs.getString(tableAlias + ".instrument_type"))
val productType = ProductType.withName(rs.getString(tableAlias + ".product_type"))
Instrument(symbol, fixRepresentation, quoteCurrency, pointsValue, instrumentType, productType)
}
}
ดังนั้นฉันคิดว่าการตั้งค่าของฉันไปข้างหน้าคือการใช้การแจงนับเมื่อค่ามีวัตถุประสงค์ที่จะเข้าถึงได้จากพื้นที่เก็บข้อมูลและกรณีวัตถุ / ลักษณะผนึก
ฉันชอบcase objects
(มันเป็นเรื่องของการตั้งค่าส่วนตัว) เพื่อจัดการกับปัญหาที่เกิดขึ้นกับวิธีการนั้น (แยกวิเคราะห์สตริงและวนซ้ำตามองค์ประกอบทั้งหมด) ฉันได้เพิ่มบางบรรทัดที่ไม่สมบูรณ์แบบ แต่มีประสิทธิภาพ
ฉันกำลังวางรหัสที่นี่โดยคาดหวังว่ามันจะมีประโยชน์และคนอื่น ๆ ก็สามารถปรับปรุงได้
/**
* Enum for Genre. It contains the type, objects, elements set and parse method.
*
* This approach supports:
*
* - Pattern matching
* - Parse from name
* - Get all elements
*/
object Genre {
sealed trait Genre
case object MALE extends Genre
case object FEMALE extends Genre
val elements = Set (MALE, FEMALE) // You have to take care this set matches all objects
def apply (code: String) =
if (MALE.toString == code) MALE
else if (FEMALE.toString == code) FEMALE
else throw new IllegalArgumentException
}
/**
* Enum usage (and tests).
*/
object GenreTest extends App {
import Genre._
val m1 = MALE
val m2 = Genre ("MALE")
assert (m1 == m2)
assert (m1.toString == "MALE")
val f1 = FEMALE
val f2 = Genre ("FEMALE")
assert (f1 == f2)
assert (f1.toString == "FEMALE")
try {
Genre (null)
assert (false)
}
catch {
case e: IllegalArgumentException => assert (true)
}
try {
Genre ("male")
assert (false)
}
catch {
case e: IllegalArgumentException => assert (true)
}
Genre.elements.foreach { println }
}
สำหรับผู้ที่ยังคงมองหาวิธีที่จะได้รับคำตอบของ GatesDa ให้ทำงาน : คุณสามารถอ้างอิงวัตถุเคสหลังจากที่ประกาศให้ยกตัวอย่าง:
trait Enum[A] {
trait Value { self: A =>
_values :+= this
}
private var _values = List.empty[A]
def values = _values
}
sealed trait Currency extends Currency.Value
object Currency extends Enum[Currency] {
case object EUR extends Currency;
EUR //THIS IS ONLY CHANGE
case object GBP extends Currency; GBP //Inline looks better
}
ผมคิดว่าประโยชน์ที่ใหญ่ที่สุดของการมีcase classes
มากกว่าenumerations
คือการที่คุณสามารถใช้รูปแบบคลาสประเภท aka เฉพาะกิจ polymorphysm ไม่จำเป็นต้องจับคู่ enums เช่น:
someEnum match {
ENUMA => makeThis()
ENUMB => makeThat()
}
แต่คุณจะมีสิ่งที่ชอบ:
def someCode[SomeCaseClass](implicit val maker: Maker[SomeCaseClass]){
maker.make()
}
implicit val makerA = new Maker[CaseClassA]{
def make() = ...
}
implicit val makerB = new Maker[CaseClassB]{
def make() = ...
}