247

Scalaでケースクラス(またはケースオブジェクト)を使用する場合と列挙を拡張する場合のベストプラクティスのガイドラインはありますか?

それらは同じ利点のいくつかを提供するようです。

4

14 に答える 14

235

大きな違いの1つはEnumeration、いくつかの文字列からそれらをインスタンス化するためのサポートが付属していることnameです。例えば:

object Currency extends Enumeration {
   val GBP = Value("GBP")
   val EUR = Value("EUR") //etc.
} 

次に、次のことができます。

val ccy = Currency.withName("EUR")

これは、列挙を(たとえば、データベースに)永続化する場合、またはファイルにあるデータから列挙を作成する場合に役立ちます。ただし、一般的に、Scalaでは列挙が少し不器用で、アドオンが扱いにくいと感じるので、今ではcase objectsを使用する傾向があります。Acase objectは列挙型よりも柔軟性があります。

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(code)」パターンに関しては、Currencyタイプの閉集合の性質を「壊す」以外に、通貨コード文字列が見つからないことを処理する他の方法があります。UnknownCurrencyタイプであるCurrencyことは、APIの他の部分に忍び込むことができるようになりました。

そのケースを外部に押し出しEnumeration、クライアントに、Option[Currency]実際に一致する問題があることを明確に示すタイプを処理させ、APIのユーザーが自分でそれを整理するように「奨励」することをお勧めします。

ここで他の答えをフォローアップするために、case objectsよりEnumerationsの主な欠点は次のとおりです。

  1. 「列挙」のすべてのインスタンスを反復処理することはできません。これは確かに事実ですが、これが必要になることは実際には非常にまれであることがわかりました。

  2. 永続化された値から簡単にインスタンス化することはできません。これも当てはまりますが、巨大な列挙(たとえば、すべての通貨)の場合を除いて、これは大きなオーバーヘッドを示しません。

于 2009-12-14T09:27:49.393 に答える
73

更新:以下に概説するソリューションよりもはるかに優れた、 新しいマクロベースのソリューションが作成されました。この新しいマクロベースのソリューションを使用することを強くお勧めします。そして、Dottyの計画により、このスタイルの列挙型ソリューションが言語の一部になるようです。うわぁ!

概要: Scalaプロジェクト内で
Javaを再現しようとするための3つの基本的なパターンがあります。Enum3つのパターンのうちの2つ。EnumJavaとを直接使用してscala.Enumeration、Scalaの徹底的なパターンマッチングを有効にすることはできません。そして3つ目。「封印されたトレイト+ケースオブジェクト」はありますが、JVMクラス/オブジェクトの初期化が複雑になり、順序インデックスの生成に一貫性がなくなります。

2つのクラスでソリューションを作成しました。このGistにあるEnumerationEnumerationDecorated。列挙用のファイルが非常に大きかったため、このスレッドにコードを投稿しませんでした(+400行-実装コンテキストを説明するコメントがたくさん含まれています)。 詳細: あなたが尋ねている質問はかなり一般的です。「...クラスを使用する場合と拡張する場合」。そして、多くの可能な答えがあり、それぞれの答えは、あなたが持っている特定のプロジェクト要件の微妙さに依存していることがわかります。答えは3つの基本的なパターンに減らすことができます。


caseobjects[scala.]Enumeration

まず、列挙とは何かという同じ基本的な考え方に基づいて作業していることを確認しましょう。Enum主にJava5 (1.5)の時点で提供されている用語で列挙型を定義しましょう。

  1. これには、名前付きメンバーの自然に順序付けられた閉集合が含まれています
    1. メンバーの数は決まっています
    2. メンバーは自然に順序付けられ、明示的に索引付けされます
      • いくつかのinateメンバーのクエリ可能な基準に基づいてソートされるのとは対照的に
    3. 各メンバーは、すべてのメンバーのセット全体の中で一意の名前を持っています
  2. すべてのメンバーは、インデックスに基づいて簡単に繰り返すことができます
  3. メンバーは、その(大文字と小文字を区別する)名前で取得できます
    1. 大文字と小文字を区別しない名前でメンバーを取得できれば非常に便利です。
  4. メンバーはそのインデックスで取得できます
  5. メンバーは、シリアル化を簡単、透過的、効率的に使用できます
  6. メンバーは、追加の関連するシングルトンネスデータを保持するように簡単に拡張できます
  7. Javaの枠を超えて考えるEnumと、Scalaのパターンマッチングの網羅性チェックを明示的に活用して列挙を行うことができれば便利です。

次に、投稿された3つの最も一般的なソリューションパターンの要約バージョンを見てみましょう

。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;
  }
}

列挙型定義の次の項目は使用できません。

  1. 3.1-大文字と小文字を区別しない名前でメンバーを取得できれば非常に便利です
  2. 7-Javaの列挙型を超えて考えると、列挙型の網羅性チェックに一致するScalaのパターンを明示的に活用できると便利です。

私の現在のプロジェクトでは、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}
}

列挙型定義の次の項目は使用できません。

  1. 1.2-メンバーは自然に順序付けられ、明示的に索引付けされます
  2. 2-すべてのメンバーは、インデックスに基づいて簡単に繰り返すことができます
  3. 3-メンバーは(大文字と小文字を区別する)名前で取得できます
  4. 3.1-大文字と小文字を区別しない名前でメンバーを取得できれば非常に便利です
  5. 4-メンバーはそのインデックスで取得できます

列挙型の定義項目5および6を実際に満たしていることは議論の余地があります。5の場合、それが効率的であると主張するのは一筋縄ではいきません。6の場合、追加の関連するシングルトンネスデータを保持するために拡張することは実際には簡単ではありません。


C)パターンの使用(このStackOverflowの回答scala.Enumerationに触発された):

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列挙型を直接使用する場合のリストと同じになる可能性があります)。

  1. 3.1-大文字と小文字を区別しない名前でメンバーを取得できれば非常に便利です
  2. 7-Javaの列挙型を超えて考えると、列挙型の網羅性チェックに一致するScalaのパターンを明示的に活用できると便利です。

繰り返しますが、現在のプロジェクトでは、列挙型メンバーを追加/削除する場合、または既存の列挙型メンバーを処理するための新しいコードを記述している場合に、コンパイル時の問題をキャッチできるようにするために、項目7が重要です。


したがって、上記の列挙型の定義を考えると、上記の3つのソリューションはいずれも、上記の列挙型の定義で概説されているすべてを提供するわけではないため、機能しません。

  1. Scala/Javaの混合プロジェクトで直接Java列挙型
  2. 「封印されたトレイト+ケースオブジェクト」
  3. scala.Enumeration

これらの各ソリューションは、最終的には再加工/拡張/リファクタリングして、それぞれの不足している要件の一部をカバーしようとすることができます。ただし、JavaEnumscala.Enumerationソリューションも、項目7を提供するために十分に拡張することはできません。そして私自身のプロジェクトにとって、これはScala内で閉じた型を使用することのより説得力のある価値の1つです。実稼働ランタイムの例外/失敗からコードを収集するのではなく、コードにギャップ/問題があることを示すために、コンパイル時の警告/エラーを強く好みます。


その点で、私はcase objectパスウェイを使用して、上記の列挙型の定義すべてをカバーするソリューションを作成できるかどうかを確認することに着手しました。最初の課題は、JVMクラス/オブジェクトの初期化の問題の核心を突き抜けることでした(このStackOverflowの投稿で詳しく説明されています)。そして、私はついに解決策を見つけることができました。

私の解決策は2つの特徴です。EnumerationEnumerationDecoratedであり、Enumerationトレイトの長さが+400行を超えているため(コンテキストを説明するコメントがたくさんあります)、このスレッドに貼り付けるのをやめています(これによりページがかなり伸びます)。詳細については、Gistに直接ジャンプしてください。

これが、上記と同じデータのアイデア(完全にコメント化されたバージョンがここにあります)を使用して、で実装されたソリューションの最終的な外観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にある)列挙特性の新しいペアの使用例です。

表明された懸念の1つは、列挙型メンバー名を繰り返す必要があることです(decorationOrderedSet上記の例では)。私はそれを1回の繰り返しに最小化しましたが、2つの問題のために、それをさらに少なくする方法がわかりませんでした。

  1. この特定のオブジェクト/ケースオブジェクトモデルのJVMオブジェクト/クラスの初期化は未定義です(このStackoverflowスレッドを参照)
  2. メソッドから返されるコンテンツの順序は未定義です(ソースコードの宣言getClass.getDeclaredClassesと同じ順序になる可能性はほとんどありません)。case object

これらの2つの問題を考えると、暗黙の順序を生成することをあきらめ、クライアントにある種の順序集合の概念で定義および宣言することを明示的に要求する必要がありました。Scalaコレクションには挿入順序セットの実装がないため、私ができる最善の方法は、Listそれが本当にセットであるかどうかを実行時にチェックすることでした。それは私がこれを達成することを望んでいた方法ではありません。

そして、この2番目のリスト/セットの順序が必要な設計を考えると、上記valChessPiecesEnhancedDecorated例では、を追加してから追加するのをcase object PAWN2 extends Member忘れることができました。したがって、リストがセットであるだけでなく、を拡張するすべてのケースオブジェクトが含まれていることを確認するためのランタイムチェックがあります。それは、特別な形の反射/マクロ地獄でした。要旨 にコメントやフィードバックを残してください。Decoration(PAWN2,'P2', 2)decorationOrderedSetsealed trait Member


于 2014-09-18T22:47:32.207 に答える
62

ケースオブジェクトはすでに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") {}
}

不正行為を気にしない場合は、reflectionAPIまたはGoogleReflectionsなどを使用して列挙値をプリロードできます。遅延のないケースオブジェクトは、最もクリーンな構文を提供します。

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列挙のすべての利点を備えた、すっきりとしたきれいなもの。個人的には、オブジェクトの外部で列挙値を定義して、慣用的なScalaコードとより一致させます。

object Currency extends Enum[Currency]
sealed trait Currency extends Currency.Value
case object EUR extends Currency
case object GBP extends Currency
于 2011-02-10T15:18:23.553 に答える
30

列挙よりもケースクラスを使用する利点は次のとおりです。

  • 封印されたケースクラスを使用する場合、Scalaコンパイラは、一致が完全に指定されているかどうかを判断できます。たとえば、一致する宣言ですべての可能な一致が支持されている場合などです。列挙では、Scalaコンパイラーはわかりません。
  • ケースクラスは、名前とIDをサポートする値ベースの列挙よりも多くのフィールドを自然にサポートします。

ケースクラスの代わりに列挙を使用する利点は次のとおりです。

  • 列挙型は、通常、記述するコードが少し少なくなります。
  • 列挙型は他の言語で普及しているため、Scalaを初めて使用する人にとっては少し理解しやすいです。

したがって、一般に、名前ごとの単純な定数のリストが必要な場合は、列挙型を使用します。それ以外の場合、もう少し複雑なものが必要な場合、またはすべての一致が指定されているかどうかをコンパイラーが安全に通知する必要がある場合は、ユースケースクラスを使用します。

于 2009-12-14T08:01:18.697 に答える
15

更新:以下のコードにはバグがあり、ここで説明されています。以下のテストプログラムは機能しますが、たとえばDayOfWeek自体の前にDayOfWeek.Monを使用すると、DayOfWeekが初期化されていないため失敗します(内部オブジェクトを使用しても外部オブジェクトは初期化されません)。val enums = Seq( DayOfWeek )メインクラスのように列挙型の初期化を強制する場合、またはchaotic3quilibriumの変更を使用する場合は、このコードを引き続き使用できます。マクロベースの列挙型を楽しみにしています!


お望みならば

  • 非網羅的なパターンの一致に関する警告
  • オプションで制御できる各列挙値に割り当てられたIntID
  • 列挙値の不変リスト(定義された順序)
  • 名前から列挙値への不変のマップ
  • idからenum値への不変のマップ
  • すべてまたは特定の列挙値、または列挙全体のメソッド/データを貼り付ける場所
  • 順序付けられた列挙値(たとえば、日<水曜日かどうかをテストできます)
  • 1つの列挙型を拡張して他の列挙型を作成する機能

次に、以下が興味深いかもしれません。フィードバックを歓迎します。

この実装には、拡張する抽象EnumおよびEnumVal基本クラスがあります。これらのクラスはすぐにわかりますが、最初に、列挙型を定義する方法は次のとおりです。

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

それを実現するには、各列挙値を使用する必要があることに注意してください(applyメソッドを呼び出します)。[特に要求しない限り、内側のオブジェクトが怠惰にならないようにしたいと思います。おもう。]

もちろん、必要に応じて、DayOfWeek、Val、または個々のケースオブジェクトにメソッド/データを追加することもできます。

そして、このような列挙型をどのように使用するかを次に示します。

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

このような警告が不要な場合は、「daymatch」を「(day:@unchecked)match」に置き換えるか、最後にキャッチオールケースを含めることができます。

上記のプログラムを実行すると、次の出力が得られます。

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抽象化と列挙型自体に追加するより高度な使用法です。

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)
}
于 2011-12-23T19:54:14.860 に答える
13

ここに、独自の値のリストを維持することなく、列挙型の値として封印された特性/クラスを使用できる、優れた単純なライブラリがあります。これは、バギーに依存しない単純なマクロに依存していknownDirectSubclassesます。

https://github.com/lloydmeta/enumeratum

于 2014-12-12T10:38:00.967 に答える
10

2017年3月の更新:Anthony Acciolyのコメントにより、scala.Enumeration/enumPRは終了しました。

Dotty(Scala用の次世代コンパイラ)が主導権を握りますが、 dottyの問題は1970年 MartinOderskyのPRは1958年です。


注:現在(2016年8月、6年以上後)削除の提案がありscala.Enumerationます: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

軽減されていない災害を非推奨にしますscala.Enumeration

scala.Enumerationに対する@enumの利点:

  • 実際に動作します
  • Java相互運用
  • 消去の問題はありません
  • 列挙を定義するときに学ぶための紛らわしいミニDSLはありません

短所:なし。

Scala.jsこれは、Scala-JVMとScala-Nativeをサポートする1​​つのコードベースを持つことができないという問題に対処します(JavaソースコードはサポートされていScala.js/Scala-Nativeません。Scalaソースコードは、Scala-JVM上の既存のAPIで受け入れられる列挙を定義できません)。

于 2016-08-19T12:45:02.117 に答える
8

すべてのインスタンスにわたって反復またはフィルタリングする必要がある場合の、ケースクラスと列挙のもう1つの欠点。これは列挙型(およびJava列挙型)の組み込み機能ですが、ケースクラスはそのような機能を自動的にサポートしません。

言い換えると、「ケースクラスを使用して列挙値の全セットのリストを取得する簡単な方法はありません」。

于 2009-12-14T15:23:26.227 に答える
6

他のJVM言語(Javaなど)との相互運用性を維持することを真剣に考えている場合、最良のオプションはJava列挙型を作成することです。scala.Enumerationこれらは、ScalaコードとJavaコードの両方から透過的に機能します。これは、オブジェクトやケースオブジェクトについて言うことができる以上のものです。回避できるのであれば、GitHubの新しいホビープロジェクトごとに新しい列挙型ライブラリを用意しないでください。

于 2013-11-21T01:08:44.993 に答える
4

ケースクラスを列挙型に模倣するさまざまなバージョンを見てきました。これが私のバージョンです:

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

たぶん、誰かが私がしたように単に各ケースクラスをリストに追加するよりも良いトリックを思い付くかもしれません。当時私が思いついたのはこれだけでした。

于 2010-12-27T20:22:51.343 に答える
3

私は好きです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 }
}
于 2015-04-06T22:25:44.720 に答える
2

私はこれらの2つのオプションを必要としていた最後の数回、行ったり来たりしてきました。最近まで、私の好みは封印されたトレイト/ケースオブジェクトオプションでした。

1)Scala列挙宣言

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

これらのどちらも、Java列挙型が提供するすべての機能を実際に満たしているわけではありませんが、以下に長所と短所を示します。

Scala列挙

長所:-オプションを使用してインスタンス化する機能、または正確であると直接想定する機能(永続ストアからロードする場合に簡単)-すべての可能な値の反復がサポートされます

短所:-全数検索のコンパイル警告はサポートされていません(パターンマッチングが理想的ではなくなります)

ケースオブジェクト/封印された特性

長所:-封印された特性を使用して、一部の値を事前にインスタンス化でき、他の値は作成時に注入できます-パターンマッチングの完全なサポート(メソッドの適用/適用解除が定義されています)

短所:-永続ストアからのインスタンス化-ここでパターンマッチングを使用するか、すべての可能な「列挙値」の独自のリストを定義する必要があります。

最終的に私の意見を変えたのは、次のスニペットのようなものでした。

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

したがって、今後の私の好みは、値がリポジトリからアクセスされることを意図している場合は列挙を使用し、それ以外の場合はオブジェクト/封印された特性をケースに入れることだと思います。

于 2012-11-29T18:04:11.433 に答える
0

GatesDaの回答を機能させる方法をまだ探している人のために:インスタンス化するために宣言した後、caseオブジェクトを参照することができます:

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
}
于 2016-05-06T15:42:08.637 に答える
0

case classesオーバーすることの最大の利点は、型クラスパターン(別名アドホック多形enumerations)を使用できることだと思います。次のような列挙型に一致する必要はありません。

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() = ...
}
于 2018-12-05T12:00:15.500 に答える