प्रकार-सुरक्षित एनम प्रकार कैसे मॉडल करें?


311

enumजावा की तरह स्काला के पास टाइप-सेफ नहीं है। संबंधित स्थिरांक के एक सेट को देखते हुए, उन स्थिरांक का प्रतिनिधित्व करने के लिए स्काला में सबसे अच्छा तरीका क्या होगा?


2
सिर्फ जावा एनम का उपयोग क्यों नहीं? यह उन कुछ चीजों में से एक है जिन्हें मैं अब भी सादे जावा का उपयोग करना पसंद करता हूं।
मैक्स

1
मैंने स्काला एन्यूमरेशन और विकल्प के बारे में एक छोटा सा विवरण लिखा है, आप इसे उपयोगी पा सकते हैं: pedrorijo.com/blog/scala-enums/
pedrorijo91

जवाबों:


187

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
  }

2
गंभीरता से, एप्लिकेशन का उपयोग नहीं किया जाना चाहिए। यह तय नहीं था; एक नया वर्ग, App पेश किया गया था, जिसमें उन समस्याओं का उल्लेख नहीं किया गया है जिनका Schildmeijer ने उल्लेख किया है। तो "वस्तु फू ऐप का विस्तार करता है {...}" और आपके पास कमांड लाइन तर्क के माध्यम से तत्काल पहुंच है।
अमीगोनिको

scala.Enumeration (जो आप अपने "ऑब्जेक्ट WeekDay" कोड नमूने में ऊपर उपयोग कर रहे हैं) थकावट पैटर्न मिलान की पेशकश नहीं करता है। मैंने वर्तमान में स्काला में उपयोग किए जा रहे सभी अलग-अलग गणन प्रतिमानों पर शोध किया है और इस स्टैकऑवरफ्लो उत्तर में एक नया पैटर्न (जो एक नया पैटर्न प्रदान करता है, जिसमें से दोनों scala.Enumeration और "सीलबंद विशेषता + ऑब्जेक्ट" पैटर्न: स्टैकोरव्यूफ़्लो सहित) का अवलोकन और अवलोकन करते हैं । com / a / 25923651/501113
chaotic3quilibrium

377

मुझे कहना होगा कि स्कैफ़मैन द्वारा स्कैला दस्तावेज़ीकरण के ऊपर कॉपी किया गया उदाहरण व्यवहार में सीमित उपयोगिता का है (आप उपयोग कर सकते हैं )।case object

एक जावा Enum(यानी समझदार toStringऔर valueOfतरीकों के साथ) के सबसे निकट से कुछ प्राप्त करने के लिए - शायद आप एक डेटाबेस के लिए एनुम मूल्यों को बरकरार रख रहे हैं) आपको इसे थोड़ा संशोधित करने की आवश्यकता है। अगर आपने 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

7
Btw।
मान

36
@macias valueOfका प्रतिस्थापन है withName, जो एक विकल्प को वापस नहीं करता है, और एक एनएसई फेंकता है अगर कोई मैच नहीं होता है। क्या!
Bluu

6
एक विकल्प है करने के लिए = WeekDay.values.find (_ toString == नाम।) @Bluu आप valueOf अपने आप को जोड़ सकते हैं:: डीईएफ़ valueOf (स्ट्रिंग नाम)
सबसे तेजी से

@centr जब मैं एक बनाने की कोशिश करता हूं Map[Weekday.Weekday, Long]और एक मान जोड़ता हूं Monतो यह कंपाइलर एक अमान्य प्रकार की त्रुटि को फेंकता है। वीकएंड की उम्मीद है। ऐसा क्यों होता है?
सोहेब

@ सोहेब यह नक्शा [सप्ताह के अंत में होना चाहिए। लंबी, लंबी]।
सेंट

98

करने के कई तरीके हैं।

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 /

दुर्भाग्य से, यह सुनिश्चित नहीं करता है कि सभी मैचों के लिए जिम्मेदार हैं। अगर मैं मैच में रो या कॉलम लगाना भूल जाता, तो स्काला कंपाइलर मुझे चेतावनी नहीं देता। तो यह मुझे कुछ प्रकार की सुरक्षा देता है , लेकिन उतना नहीं जितना कि प्राप्त किया जा सकता है।

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 /

आप आश्चर्यचकित हो सकते हैं, फिर, कभी भी केस ऑब्जेक्ट्स के बजाय एन्यूमरेशन का उपयोग क्यों करें। तथ्य की बात के रूप में, केस ऑब्जेक्ट्स के कई बार फायदे हैं, जैसे कि यहां। गणना वर्ग, हालांकि, कई संग्रह विधियां हैं, जैसे कि तत्व (स्केल 2.8 पर पुनरावृत्ति), जो एक Iterator, मानचित्र, फ़्लैटमैप, फ़िल्टर, आदि देता है।

यह उत्तर अनिवार्य रूप से मेरे ब्लॉग में इस लेख से चयनित भागों है ।


"... गैर-प्रतीकों को स्वीकार नहीं करना जहां एक प्रतीक की उम्मीद है"> मैं आपको अनुमान लगा रहा हूं कि Symbolउदाहरणों में रिक्त स्थान या विशेष वर्ण नहीं हो सकते। ज्यादातर लोग जब पहली बार Symbolकक्षा का सामना करते हैं तो शायद ऐसा सोचते हैं, लेकिन वास्तव में गलत है। Symbol("foo !% bar -* baz")संकलन करता है और पूरी तरह से ठीक चलता है। दूसरे शब्दों में, आप किसी भी स्ट्रिंग को Symbolलपेटने वाले उदाहरणों को पूरी तरह से बना सकते हैं (आप इसे "सिंगल कोमा" सिंथैटिक शुगर के साथ नहीं कर सकते हैं)। केवल एक चीज जो गारंटी देती है, वह है किसी भी प्रतीक की विशिष्टता, तुलनात्मक रूप से तुलना करना और मैच करना। Symbol
रेगीस जीन-गिल्स

@ RégisJean-Gilles नहीं, मेरा मतलब है कि आप पैरामीटर के Stringतर्क के रूप में, उदाहरण के लिए, पास नहीं कर सकते Symbol
डैनियल सी। सोबरल

हां, मैं उस हिस्से को समझ गया था, लेकिन यह एक बहुत ही मजेदार बिंदु है यदि आप Stringकिसी अन्य वर्ग से प्रतिस्थापित करते हैं जो मूल रूप से एक स्ट्रिंग के चारों ओर एक आवरण है और दोनों दिशाओं में स्वतंत्र रूप से परिवर्तित किया जा सकता है (जैसा कि मामला है Symbol)। मुझे लगता है कि आपके कहने का मतलब यह है कि "यह आपको किसी भी प्रकार की सुरक्षा नहीं देगा", यह सिर्फ इतना स्पष्ट नहीं था कि ओपी ने स्पष्ट रूप से प्रकार के समाधान के लिए कहा था। मुझे यकीन नहीं था कि लिखने के समय आप जानते थे कि न केवल यह सुरक्षित नहीं है, क्योंकि वे बिल्कुल भी नहीं हैं, लेकिन यह Symbol भी गारंटी नहीं है कि पारित तर्क में विशेष वर्ण नहीं होंगे।
रेगीस जीन-गिल्स

1
विस्तृत करने के लिए, जब आप कहते हैं "गैर-प्रतीकों को स्वीकार नहीं करना जहां एक प्रतीक की उम्मीद है", इसे या तो "मानों को स्वीकार नहीं करना है जो कि प्रतीक के उदाहरण नहीं हैं" (जो स्पष्ट रूप से सच है) या "उन मूल्यों को स्वीकार नहीं करना जो नहीं हैं" सादा पहचानकर्ता की तरह तार, उर्फ 'प्रतीकों "(जो सच नहीं है, और एक गलत धारणा है कि किसी को भी काफी तथ्य यह है कि पहली मुठभेड़ हालांकि खास है की वजह से, पहली बार हम स्केला प्रतीकों का सामना किया है 'fooअंकन जो करता रोकना गैर-पहचानकर्ता तार)। यह इस गलत धारणा है कि मैं किसी भी भविष्य के पाठक के लिए दूर करना चाहता था।
रेजिस जीन-गिल्स

@ RégisJean-Gilles मैं पूर्व का मतलब था, जो स्पष्ट रूप से सच है। मेरा मतलब है, यह स्पष्ट रूप से किसी के लिए सच है जो स्थैतिक टाइपिंग के लिए उपयोग किया जाता है। इसके बाद स्थिर और "गतिशील" टाइपिंग के सापेक्ष गुणों पर बहुत चर्चा हुई और स्काला में रुचि रखने वाले बहुत से लोग एक गतिशील टाइपिंग पृष्ठभूमि से आए, इसलिए मुझे लगा कि यह बिना कहे नहीं चलेगा। मैं भी आजकल वह टिप्पणी करने के बारे में नहीं सोचता। व्यक्तिगत रूप से, मुझे लगता है कि स्काला का प्रतीक बदसूरत और निरर्थक है, और इसका उपयोग कभी नहीं करें। मैं आपकी पिछली टिप्पणी को बढ़ा रहा हूं, क्योंकि यह एक अच्छा बिंदु है।
डेनियल सी। सोबराल

52

नामांकित घोषित करने का थोड़ा कम क्रिया तरीका:

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

निश्चित रूप से यहां समस्या यह है कि आपको नाम और वैल के क्रम को सिंक में रखने की आवश्यकता होगी जो कि एक ही लाइन पर नाम और वैल घोषित होने पर करना आसान है।


11
यह पहली नज़र में क्लीनर लग रहा है, लेकिन अनुरक्षक को दोनों सूचियों के ओडर को सिंक में रखने की आवश्यकता का नुकसान है। सप्ताह उदाहरण के दिनों के लिए, यह संभावना नहीं दिखाई देती है। लेकिन सामान्य तौर पर, एक नया मूल्य डाला जा सकता है, या एक हटा दिया गया है और दो सूचियां सिंक से बाहर हो सकती हैं, जिस स्थिति में, सूक्ष्म कीड़े पेश किए जा सकते हैं।
ब्रेंट फस्ट

1
पूर्व टिप्पणी के अनुसार, जोखिम दो अलग-अलग सूचियां हैं जो चुपचाप सिंक से बाहर जा सकती हैं। हालांकि यह आपके वर्तमान छोटे उदाहरण के लिए कोई समस्या नहीं है, अगर कई और सदस्य हैं (जैसे कि दर्जनों से सैकड़ों में), तो दो सूचियों के चुपचाप सिंक से बाहर जाने की संभावना काफी अधिक है। इसके अलावा scala.Enumeration स्कैला के संकलन समय निकास पैटर्न से चेतावनी / त्रुटियों से लाभ नहीं उठा सकता है। मैंने एक StackOverflow उत्तर बनाया है जिसमें दो सूचियों को सिंक में बने रहने के लिए एक रनटाइम चेक करने वाला एक समाधान शामिल है: stackoverflow.com/a/25923651/501113
chaotic3quilibrium

17

आप संयुक्ताक्षर के बजाय एक सील अमूर्त वर्ग का उपयोग कर सकते हैं, उदाहरण के लिए:

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))
  }

}

मामले की वस्तुओं के साथ मुहरबंद लक्षण भी एक संभावना है।
आशालैंड

2
"सील विशेषता + केस ऑब्जेक्ट्स" पैटर्न में ऐसे मुद्दे हैं जो मैं एक स्टैकऑवरफ्लो उत्तर में विस्तार करता हूं। हालाँकि, मैंने यह पता लगाया कि इस पैटर्न से जुड़े सभी मुद्दों को कैसे हल किया जाए जो कि धागे में भी शामिल है: stackoverflow.com/a/25923651/501113
chaotic3quilibrium

7

बस खोजा हुआ प्रगंड । यह बहुत अद्भुत है और समान रूप से आश्चर्यजनक है यह अधिक प्रसिद्ध नहीं है!


2

स्काला में "एनुमरेशंस" के आसपास सभी विकल्पों पर गहन शोध करने के बाद, मैंने इस डोमेन के एक और स्टैक ऑवरऑवरफ्लो थ्रेड पर बहुत अधिक संपूर्ण अवलोकन पोस्ट किया । इसमें "सीलबंद विशेषता + केस ऑब्जेक्ट" पैटर्न का एक समाधान शामिल है जहां मैंने जेवीएम वर्ग / ऑब्जेक्ट इनिशियलाइज़ेशन ऑर्डरिंग समस्या को हल किया है।



1

स्काला में यह 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)
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.