টাইপ-সেফ এনাম প্রকারগুলি কীভাবে মডেল করবেন?


311

স্কালায় enumজাভা যেমন টাইপ-সেফ নেই । সম্পর্কিত ধ্রুবকগুলির একটি সেট দেওয়া, ala ধ্রুবকদের প্রতিনিধিত্ব করার জন্য স্কালার সর্বোত্তম উপায় কী হবে?


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
গুরুতরভাবে, অ্যাপ্লিকেশন ব্যবহার করা উচিত নয়। এটি ঠিক করা হয়নি; অ্যাপ্লিকেশন নামে একটি নতুন ক্লাস চালু হয়েছিল, যার মধ্যে শিল্ডমাইজারের উল্লেখ করা সমস্যা নেই। সুতরাং "অবজেক্ট foo অ্যাপ্লিকেশন প্রসারিত করে {...}" এবং আপনার কাছে আরগ ভেরিয়েবলের মাধ্যমে কমান্ড-লাইন আর্গুমেন্টে তাত্ক্ষণিক অ্যাক্সেস রয়েছে।
AmigoNico

scala.Eeumeration (যা আপনি উপরের "অবজেক্ট উইকডে" কোড নমুনায় ব্যবহার করছেন) নিখুঁত প্যাটার্ন মিলছে না। আমি বর্তমানে স্ক্যালায় ব্যবহৃত সমস্ত পৃথক গণনা নিদর্শনগুলি নিয়ে গবেষণা করেছি এবং এই স্ট্যাকওভারফ্লো উত্তরে তাদের একটি সংক্ষিপ্ত বিবরণ দিয়েছি এবং সংক্ষিপ্তসার দিয়েছি (একটি নতুন প্যাটার্ন যা উভয় ক্ষেত্রেই স্কালার সেরা প্রস্তাব দেয় n অঙ্ক এবং "সিলড বৈশিষ্ট্য + কেস অবজেক্ট" প্যাটার্ন: স্ট্যাকওভারফ্লো)। com / এ / 25923651/501113
বিশৃঙ্খল

377

আমি অবশ্যই বলব উদাহরণস্বরূপ Scala ডকুমেন্টেশন আউট কপি দ্বারা skaffman উপরে বাস্তবে সীমিত উপযোগ হয় (আপনি হয়তো পাশাপাশি ব্যবহার case objectগুলি)।

জাভা সবচেয়ে সাদৃশ্যযুক্ত কিছু পাওয়ার জন্য Enum(যেমন বুদ্ধিমান toStringএবং valueOfপদ্ধতির সাথে - সম্ভবত আপনি একটি ডাটাবেসে এনাম মানগুলি চালিয়ে যাচ্ছেন) আপনার এটিকে কিছুটা সংশোধন করতে হবে। আপনি যদি স্কাফম্যানের কোড ব্যবহার করতেন :

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
@ ম্যাকিয়াসের valueOfপ্রতিস্থাপন হ'ল withName, যা কোনও বিকল্প দেয় না এবং কোনও মিল না থাকলে কোনও এনএসই ফেলে দেয়। কি!
ব্লু

6
একটি অপশন আছে = WeekDay.values.find (: _ toString == নাম।) @Bluu আপনি valueOf নিজেকে যুক্ত করতে পারেন: Def 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 /

আপনি ভাবতে পারেন, তবে কেস অবজেক্টের পরিবর্তে কেন কখনও এনুমুরেশন ব্যবহার করবেন। প্রকৃতপক্ষে, কেস অবজেক্টগুলির এখানে অনেকবার সুবিধা রয়েছে। এনুমারেশন ক্লাসে অনেকগুলি সংগ্রহের পদ্ধতি রয়েছে, যেমন উপাদানগুলি (স্কেলা ২.৮-এ পুনরুক্তি করা), যা একটি আইট্রেটর, মানচিত্র, ফ্ল্যাটম্যাপ, ফিল্টার ইত্যাদি প্রদান করে returns

এই উত্তরটি মূলত আমার ব্লগে এই নিবন্ধ থেকে একটি নির্বাচিত অংশ ।


"... প্রতীক প্রত্যাশিত যেখানে অ-প্রতীকগুলি গ্রহণ করবেন না"> আমি আপনার অনুমান করছি যে Symbolউদাহরণগুলির মধ্যে স্পেস বা বিশেষ অক্ষর থাকতে পারে না। বেশিরভাগ লোকেরা যখন প্রথম Symbolশ্রেণীর মুখোমুখি হয় তখন সম্ভবত এটিই মনে হয় তবে আসলে এটি ভুল। Symbol("foo !% bar -* baz")সংকলন এবং পুরোপুরি সূক্ষ্ম চালানো। অন্য কথায় আপনি নিখুঁতভাবে কোনও স্ট্রিং Symbolমোড়ানো উদাহরণ তৈরি করতে পারেন (আপনি কেবল "একক কোমা" সিনট্যাকটিক চিনি দিয়ে এটি করতে পারবেন না)। গ্যারান্টি দেয় এমন একমাত্র বিষয় হ'ল কোনও প্রদত্ত প্রতীকটির স্বতন্ত্রতা, এটি তুলনামূলকভাবে তুলনামূলকভাবে এবং ম্যাচের তুলনায় সামান্য দ্রুত তৈরি করে। Symbol
রেগিস জিন-গিলস

@ রেগিসজিন-গিলস নং, আমি বোঝাতে চাইছি যে আপনি Stringএকটি Symbolপ্যারামিটারের পক্ষে যুক্তি হিসাবে একটি পাস করতে পারবেন না ।
ড্যানিয়েল সি

হ্যাঁ, আমি সেই অংশটি বুঝতে পেরেছি, তবে আপনি যদি Stringঅন্য কোনও ক্লাসের সাথে প্রতিস্থাপন করেন যা মূলত একটি স্ট্রিংয়ের চারপাশে মোড়ক এবং উভয় দিক থেকে অবাধে রূপান্তর করা যায় (যেমনটি হয় তবে Symbol)। আমার ধারণা, "এটি আপনাকে কোনও ধরণের সুরক্ষা দেবে না" বলার সময় আপনি কী বোঝাতে চেয়েছিলেন, ওপি স্পষ্টভাবে টাইপ নিরাপদ সমাধানের জন্য জিজ্ঞাসা করলেও এটি খুব পরিষ্কার ছিল না। আমি নিশ্চিত ছিলাম না যে লেখার সময় আপনি জানতেন যে এটি কেবল নিরাপদ টাইপ নয় কারণ সেগুলি মোটেই এনাম নয়, তবে এটিও Symbol গ্যারান্টি দেয় না যে পাস হওয়া যুক্তিতে বিশেষ অক্ষর থাকবে না।
রেগিস জিন-গিলস

1
বিশদভাবে বলার জন্য, আপনি যখন "প্রতীক প্রত্যাশিত যেখানে অ-প্রতীকগুলি গ্রহণ না করা" বলছেন, তখন এটি "প্রতীকগুলির উদাহরণ নয় এমন মানগুলি গ্রহণ না করা" (যা প্রকৃতপক্ষে সত্য) বা "মানগুলি মান্য না করে" হিসাবে পড়া যেতে পারে প্লেইন আইডেন্টিফায়ার মত স্ট্রিং, ওরফে 'চিহ্ন' "(যা সত্য নয়, এবং একটি ভুল ধারণা যে প্রায় কাছাকাছি কেহ যে প্রথম এনকাউন্টার যদিও বিশেষ কারণে, প্রথমবার আমরা Scala প্রতীক সম্মুখীন হয়েছে 'fooস্বরলিপি যা করে প্রতিরোধ অ-সনাক্তকারী স্ট্রিং)। এটি এই ভ্রান্ত ধারণাটি যে আমি ভবিষ্যতের যে কোনও পাঠকের জন্য তা দূর করতে চেয়েছিলাম।
রেগিস জিন-গিলস

@ রাগিসজিন-গিলস আমি বলতে চাইছিলাম পূর্বেরটি, যা স্পষ্টতই সত্য। আমার অর্থ, স্থির টাইপিংয়ে অভ্যস্ত যে কারও পক্ষে এটি স্পষ্টতই সত্য। এরপরে স্থিতিশীল এবং "গতিশীল" টাইপিংয়ের তুলনামূলক গুণাগুণ নিয়ে প্রচুর আলোচনা হয়েছিল এবং স্কালায় আগ্রহী প্রচুর লোকেরা একটি গতিশীল টাইপিংয়ের পটভূমি থেকে এসেছিল, তাই আমি ভেবেছিলাম এটি না বলেই যায় না। আমি আজকাল সেই মন্তব্য করার কথা ভাবিও না। ব্যক্তিগতভাবে, আমি মনে করি স্কালার প্রতীকটি কুৎসিত এবং অপ্রয়োজনীয় এবং কখনও এটি ব্যবহার করবেন না। আমি আপনার শেষ মন্তব্যটি সমর্থন করছি, কারণ এটি একটি ভাল বিষয় good
ড্যানিয়েল সি সোব্রাল

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
পূর্ববর্তী মন্তব্য অনুসারে, ঝুঁকি হ'ল দুটি ভিন্ন তালিকা নিঃশব্দে সিঙ্কের বাইরে চলে যেতে পারে। এটি আপনার বর্তমান ছোট উদাহরণের জন্য কোনও সমস্যা না হলেও, যদি আরও অনেক সদস্য থাকে (যেমন কয়েক ডজন থেকে কয়েক শতাধিক) তবে দুটি তালিকার প্রতিক্রিয়া নিঃশব্দে সিঙ্কের বাইরে চলে যাওয়ার বিষয়টি যথেষ্ট বেশি higher এছাড়াও স্কেলা n গণনাটি স্কালার সংকলন সময়ের পরিপূর্ণ প্যাটার্নের সাথে মেলে সতর্কতা / ত্রুটিগুলি থেকে উপকার করতে পারে না। : আমি একটি 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)
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.