ScalaでのCaseオブジェクトと列挙


231

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

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


2
私はscalaの列挙と代替についての簡単な概要を書きましたが、あなたはそれが役に立つかもしれません:pedrorijo.com/blog/scala-enums/
pedrorijo91

1
DottyベースのScala 3enum(2020年半ば)も参照してください。
VonC

回答:


223

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

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

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

val ccy = Currency.withName("EUR")

これは、列挙を永続化する(データベースなど)か、ファイルにあるデータから作成する場合に役立ちます。ただし、一般的に、列挙型はScalaで少し扱いに​​くく、扱いにくいアドオンのように感じるため、今ではcase objects を使用する傾向があります。A case 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. 永続化された値から簡単にインスタンス化することはできません。これも当てはまりますが、巨大な列挙(たとえば、すべての通貨)の場合を除いて、これは大きなオーバーヘッドにはなりません。


10
もう1つの違いは、列挙列挙型はすぐに注文できるのに対し、ケースオブジェクトベースの列挙型は明白にそうではないということです
om-nom-nom

1
caseオブジェクトのもう1つのポイントは、Javaの相互運用性を気にするかどうかです。Enumerationは値をEnumeration.Valueとして返すため、1)scala-libraryが必要、2)実際の型情報が失われます。
juanmirocks 2012年

7
@oxbow_lakesポイント1については、特にこの部分「これが必要になることは実際には非常にまれであることがわかりました」:どうやらUIの作業をほとんど行わないようです。これは非常に一般的な使用例です。選択する有効な列挙型メンバーの(ドロップダウン)リストを表示します。
chaotic3quilibrium 2014

trade.ccy封印された特性の例で一致するアイテムのタイプがわかりません。
ロロス

そして、case objectより大きな(〜4x)コードフットプリントを生成しないのEnumerationですか?特にscala.js小さなフットプリントを必要とするプロジェクトのための有用な区別。
ecoe

69

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

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

2つのクラスを持つソリューションを作成しました。このGistにあるEnumerationおよびEnumerationDecorated。Enumerationのファイルが非常に大きい(+400行-実装コンテキストを説明する多くのコメントが含まれている)ため、このスレッドにコードを投稿しませんでした。 詳細: あなたが尋ねている質問はかなり一般的です。"... クラスを使用する場合と拡張する場合


caseobjects[scala.]Enumeration」そして、多くの可能な答えがあり、それぞれの答えはあなたが持っている特定のプロジェクト要件の微妙さによって異なります。答えは、3つの基本パターンに減らすことができます。

最初に、列挙とは何かという同じ基本的な考え方から作業していることを確認しましょう。EnumJava 5(1.5)の時点で提供されているものに関して主に列挙を定義してみましょう:

  1. 名前付きメンバーの自然順序付けされた閉じたセットが含まれています
    1. 決まった数のメンバーがいます
    2. メンバーは自然に順序付けられ、明示的にインデックスが付けられます
      • メンバーのクエリ可能な基準に基づいてソートされるのではなく
    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のEnumを超えて考えると、列挙型の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)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を直接使用するためのリストと同じになる可能性があります)。

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

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


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

  1. Scala / Javaの混合プロジェクトで直接使用するJava Enum
  2. 「封印された特性+ケースオブジェクト」
  3. scala.Enumeration

これらの各ソリューションは、最終的には、それぞれの不足している要件の一部をカバーするように再加工/拡張/リファクタリングできます。ただし、Java Enumscala.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コレクションには挿入順のセット実装がないため、私ができる最善の方法は、a Listを使用してから、実行時にそれが本当にセットであることを確認することでした。私がこれを達成したいと思った方法ではありません。

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


コメントやフィードバックを残してください。


私は今ScalaOlioライブラリの両方のより最新のバージョンが含まれています(GPLv3の)の最初のバージョンリリースしているorg.scalaolio.util.Enumerationorg.scalaolio.util.EnumerationDecoratedscalaolio.org
chaotic3quilibrium

そして、GithubのScalaOlioリポジトリに直接移動するには:github.com/chaotic3quilibrium/scala-olio
chaotic3quilibrium

5
これは質の高い答えであり、そこから多くを得ることができます。ありがとう
angabriel 2016年

1
OderskyがDotty(将来のScala 3.0)をネイティブのenumでアップグレードしたいようです。うわー!github.com/lampepfl/dotty/issues/1970
chaotic3quilibrium

62

Caseオブジェクトは既に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リフレクションなどを使用して、列挙値をプリロードできます。レイジーでないケースオブジェクトは、最も簡潔な構文を提供します。

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

3
1つの質問:最後のソリューションは「非レイジーケースオブジェクト」と呼ばれますが、この場合、オブジェクトは使用するまで読み込まれません。なぜこのソリューションを非レイジーと呼ぶのですか?
Seb Cesbron、2012年

2
@Noel、封印された階層全体をREPLに貼り付けるには、:pasteを使用する必要があります。そうしないと、基本クラス/特性がシールされた1行が1つのファイルとしてカウントされ、すぐにシールされ、次の行で拡張できなくなります。
ユルゲン・ストローベル

2
@GatesDA最初のコードスニペットのみにバグはありません(クライアントが値を宣言して定義することを明示的に要求するためです。2番目と3番目のソリューションには、最後のコメントで説明した微妙なバグがあります(クライアントが通貨にアクセスした場合) .GBPを直接使用すると、最初に値のリストが「順不同」になります。私はScala列挙ドメインを広範囲にわたって調査し、この同じスレッドへの私の回答で詳しくカバーしています:stackoverflow.com/a/25923651/501113
chaotic3quilibrium

1
おそらく、この方法の欠点の1つ(とにかくJava Enumsと比較して)は、IDEでCurrency <dot>と入力すると、使用可能なオプションが表示されないことです。
Ivan Balashov

1
@SebCesbronが述べたように、ケースオブジェクトはここでは遅延しています。したがって、を呼び出すとCurrency.values、以前にアクセスした値のみが返されます。それを回避する方法はありますか?
Sasgorilla、

27

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

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

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

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

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


15

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


お望みならば

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

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

この実装には、抽象EnumおよびEnumVal基本クラスがあり、これらを拡張します。これらのクラスは1分後に表示されますが、最初に、列挙型を定義する方法を次に示します。

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

このような警告が不要な場合は、「日一致」を「(日:@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抽象化および列挙自体に追加する、より高度な使用方法を次に示します。

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

これを提供するためのTyvm。ほんとうにありがとう。ただし、valではなく「var」を使用していることに気づきました。そして、これはFPの世界における境界の大罪です。それで、varを使用しないようにこれを実装する方法はありますか?これが何らかのFPタイプのエッジケースであり、実装がFPにとってどのように望ましくないかを理解していない場合は、単に興味があります。
chaotic3quilibrium 2013年

2
私はおそらくあなたを助けることはできません。Scalaでは、内部的には変化するが、それらを使用するクラスに対して不変であるクラスを記述することはかなり一般的です。上記の例では、DayOfWeekのユーザーは列挙型を変更できません。たとえば、事後に火曜日のIDまたは名前を変更する方法はありません。しかし、内部的に変更のない実装が必要な場合は、何も得られません。ただし、2.11でマクロに基づいた新しいenum機能が表示されても驚くことはありません。アイデアはscala-langで動き回っています。
AmigoNico 2013年

Scala Worksheetで奇妙なエラーが発生します。Valueインスタンスの1つを直接使用すると、初期化エラーが発生します。ただし、.valuesメソッドを呼び出して列挙の内容を確認すると、それが機能し、valueインスタンスを直接使用すると機能します。初期化エラーとは何ですか?また、呼び出し規約に関係なく、初期化が正しい順序で行われるようにするための最適な方法は何ですか?
chaotic3quilibrium

@ chaotic3quilibrium:わあ!これを追求してくれてありがとう、そしてもちろん重労働をしてくれたレックスカーに感謝します。ここで問題に言及し、あなたが作成した質問を参照します。
AmigoNico 2013

「[を使用varすることはFPの世界での境界的な大罪である」—私は意見が広く受け入れられているとは思いません。
Erik Kaplun

12

ここには、独自の値のリストを維持する必要なしに、enum値として封印された特性/クラスを使用できる、素晴らしいシンプルなlibがあります。バギーに依存しない単純なマクロに依存していknownDirectSubclassesます。

https://github.com/lloydmeta/enumeratum


10

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

Dotty(Scalaの次世代コンパイラ)が主導しますが、 1970年のDottyの問題 Martin Oderskyの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ます。

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

  • 実際に動作します
  • Java相互運用
  • 消去の問題はありません
  • 列挙型を定義するときに理解するのに混乱しないmini-DSL

短所:なし。

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


上記のPRは終了しました(喜びはありません)。現在は2017年で、Dottyがようやくenumコンストラクトを取得するようです。これが問題MartinのPRです。マージ、マージ、マージ!
Anthony Accioly 2017年

8

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

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


5

他のJVM言語(Javaなど)との相互運用性の維持に真剣に取り組んでいる場合は、Java列挙型を作成するのが最善の方法です。これらはScalaとJavaコードの両方から透過的に機能します。これは、for scala.Enumerationオブジェクトまたはcaseオブジェクトとは言えません。GitHubのすべての新しい趣味プロジェクトに新しい列挙ライブラリを用意しないでください(回避できる場合)。


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

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


なぜ2つの別々のunapplyメソッドがあるのですか?
Saish

@jho私はあなたのソリューションをそのまま処理しようとしていますが、コンパイルできません。2番目のコードの抜粋には、「タイプV =サイト」のサイトへの参照があります。コンパイルエラーをクリアするために何を参照しているかはわかりません。次に、なぜ「抽象クラスCurrency」に空の括弧を提供するのですか?彼らはそのままにされなかったのではないでしょうか?最後に、なぜ「var values = ...」でvarを使用しているのですか?これは、クライアントがコードのどこからでもいつでも新しいリストを値に割り当てることができることを意味しませんか?varではなくvalにする方がはるかに望ましいのではないでしょうか。
chaotic3quilibrium '19 / 09/19

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

ですから、私の好みは、値がリポジトリからアクセスすることを意図している場合は列挙型を使用し、そうでない場合はケースオブジェクト/密封された特性を使用することだと思います。


2番目のコードパターンが望ましい(最初のコードパターンから2つのヘルパーメソッドを取り除く)ことがわかります。しかし、私はあなたがこれらの2つのパターンから選択することを強制されないような方法を見つけました。私はこのスレッドに投稿した回答でドメイン全体をカバーしています:stackoverflow.com/a/25923651/501113
chaotic3quilibrium

2

私は好みます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 }
}

0

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
}

0

私が持っていることの最大の利点だと思うcase classes以上にenumerationsあなたが使用できるということである型クラスのパターンを別名アドホックpolymorphysm。次のような列挙型を一致させる必要はありません:

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() = ...
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.