스칼라의 케이스 객체와 열거


231

스칼라에서 열거 클래스 를 확장하는 경우와 케이스 클래스 (또는 케이스 오브젝트) 를 사용하는시기에 대한 모범 사례 지침이 있습니까?

그들은 동일한 이점 중 일부를 제공하는 것 같습니다.


2
scala 열거 및 대안에 대한 작은 개요를 작성했습니다. pedrorijo.com/blog/scala-enums/
pedrorijo91

1
Dotty 기반 스칼라 3enum (2020 년 중반) 도 참조하십시오 .
VonC 2016 년

답변:


223

한 가지 큰 차이점은 Enumeration일부 name문자열 에서 인스턴스화를 지원 한다는 것입니다 . 예를 들면 다음과 같습니다.

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

그럼 당신은 할 수 있습니다 :

val ccy = Currency.withName("EUR")

이는 열거 형 (예 : 데이터베이스)을 유지하거나 파일에있는 데이터에서 열거를 작성하려는 경우에 유용합니다. 그러나 일반적으로 열거 형은 스칼라에서 약간 어색하고 어색한 추가 기능을 가지고 있으므로 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 의 주요 단점 Enumeration은 다음과 같습니다.

  1. "enumeration"의 모든 인스턴스를 반복 할 수 없습니다 . 이것은 확실히 사실이지만, 실제로 이것이 필요한 경우는 거의 없습니다.

  2. 지속 가치에서 쉽게 인스턴스화 할 수 없습니다 . 이것은 사실이지만, 모든 열거와 같은 거대한 열거의 경우를 제외하고는 큰 오버 헤드가 아닙니다.


10
또 다른 차이점은 열거 열거는 반면, 상자 밖으로 정렬되어 있다는 것입니다 경우 객체 기반 열거 obviosly하지
OM-NOM-NOM

1
케이스 객체의 또 다른 요점은 Java 상호 운용성에 관심이있는 경우입니다. Enumeration은 값을 Enumeration.Value로 반환하므로 1) 스칼라 라이브러리 필요, 2) 실제 유형 정보가 손실됩니다.
juanmirocks

7
@oxbow_lakes 포인트 1과 관련하여, 특히이 부분은 "... 실제로는 이것이 매우 드물다는 것을 발견했습니다": 분명히 UI 작업은 거의하지 않습니다. 이것은 매우 일반적인 사용 사례입니다. 선택할 유효한 열거 형 멤버의 드롭 다운 목록을 표시합니다.
chaotic3quilibrium

trade.ccy봉인 된 특성 예시에서 일치하는 항목의 유형을 이해하지 못합니다 .
rloth

case objects보다 더 큰 (~ 4 배) 코드 풋 프린트를 생성 하지 않습니다 Enumeration. scala.js작은 설치 공간이 필요한 프로젝트에 특히 유용합니다 .
ecoe

69

업데이트 : 아래에 설명 된 솔루션보다 훨씬 우수한 새로운 매크로 기반 솔루션 이 만들어졌습니다. 이 새로운 매크로 기반 솔루션을 사용하는 것이 좋습니다 . 그리고 Dotty에 대한 계획은 이러한 스타일의 열거 형 솔루션을 언어의 일부로 만들 것입니다. 우와 후!

요약 : Scala 프로젝트 내
에서 Java를 재생산하기위한 세 가지 기본 패턴이 있습니다 Enum. 세 가지 패턴 중 두 가지; 직접 자바를 사용 Enum하고 scala.Enumeration, 스칼라의 철저한 패턴 매칭을 가능하게 할 수 없습니다. 그리고 세번째; "밀봉 된 특성 + 케이스 오브젝트"는 수행하지만 JVM 클래스 / 개체 초기화 복잡성이있어 순서가 일치하지 않는 서수 인덱스 생성이 발생합니다.

두 가지 클래스로 솔루션을 만들었습니다. 이 요지 에있는 EnumerationEnumerationDecorated 입니다. 열거 형 파일이 상당히 커서 (이 라인에 400 줄-구현 컨텍스트를 설명하는 많은 주석이 들어 있으므로)이 스레드에 코드를 게시하지 않았습니다. 세부 사항 : 당신이 묻는 질문은 꽤 일반적입니다. "... 클래스 를 사용하고 확장하는 경우 " 그리고 당신이 가지고있는 특정 프로젝트 요구 사항의 미묘함에 따라 각 대답이 가능한 많은 답변이 있음이 밝혀졌습니다. 답은 세 가지 기본 패턴으로 줄일 수 있습니다.


caseobjects[scala.]Enumeration

시작하려면 열거가 무엇인지에 대한 동일한 기본 아이디어로 작업하고 있는지 확인하십시오. EnumJava 5 (1.5) 현재 제공되는 관점에서 열거를 정의합시다 .

  1. 자연스럽게 정렬 된 이름 지정된 멤버 세트를 포함합니다.
    1. 정해진 수의 회원이 있습니다
    2. 회원은 자연스럽게 주문되고 명시 적으로 색인됩니다
      • 일부 무식한 회원 쿼리 가능 기준에 따라 정렬되는 것과 대조적으로
    3. 각 멤버는 모든 멤버의 전체 세트 내에서 고유 한 이름을 갖습니다.
  2. 인덱스를 기반으로 모든 멤버를 쉽게 반복 할 수 있습니다.
  3. 대소 문자를 구분하는 이름으로 멤버를 검색 할 수 있습니다.
    1. 대소 문자를 구분하지 않는 이름으로 멤버를 검색 할 수 있다면 아주 좋을 것입니다.
  4. 인덱스를 사용하여 멤버를 검색 할 수 있습니다
  5. 멤버는 쉽고 투명하고 효율적으로 직렬화를 사용할 수 있습니다
  6. 추가 관련 단일 데이터를 보유하도록 멤버를 쉽게 확장 할 수 있음
  7. Java를 넘어 서면 Enum열거에 대해 Scala의 패턴 일치 철저 검사를 명시 적으로 활용할 수 있다면 좋을 것입니다.

: 다음은 게시 된 가장 일반적인 세 가지 솔루션 패턴의 버전 아래 삶은에서의 모습을 보자

A)를 실제로 직접 사용하여 자바Enum ) 혼합 스칼라 / 자바 프로젝트에서 패턴 (:

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은 열거 형 멤버를 추가 / 제거하거나 기존 열거 형 멤버를 처리하기 위해 새로운 코드를 작성하는 경우 컴파일 시간 문제를 포착하는 데 중요합니다.


따라서 열거에 대한 위의 정의가 주어지면 위의 세 가지 솔루션 중 어느 것도 위의 열거 정의에 요약 된 모든 것을 제공하지 않으므로 작동하지 않습니다.

  1. 혼합 스칼라 / 자바 프로젝트에서 직접 Java Enum
  2. "밀봉 된 특성 + 사례 개체"
  3. 스칼라. 열거

이러한 각 솔루션은 결국 재 작업 / 확장 / 리팩터링되어 누락 된 요구 사항 중 일부를 처리 할 수 ​​있습니다. 그러나 Java Enum또는 scala.Enumeration솔루션을 충분히 확장하여 항목 7을 제공 할 수는 없습니다. 또한 내 프로젝트의 경우 Scala에서 닫힌 유형을 사용하는 데있어 가장 강력한 가치 중 하나입니다. 컴파일 타임 경고 / 오류를 선호하여 프로덕션 런타임 예외 / 실패에서 코드를 수집하지 않고 코드에 간격 / 문제가 있음을 나타냅니다.


이와 관련하여 case object위의 열거 정의를 모두 포함하는 솔루션을 생성 할 수 있는지 확인하기 위해 경로를 사용하여 작업을 시작했습니다 . 첫 번째 과제는 JVM 클래스 / 객체 초기화 문제의 핵심 ( 이 StackOverflow post 에서 자세히 다룰 것)을 추진하는 것이 었 습니다 . 그리고 마침내 해결책을 찾을 수있었습니다.

내 솔루션은 두 가지 특성입니다. EnumerationEnumerationDecorated 이며 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위 예에서). 한 번의 반복으로 최소화했지만 두 가지 문제로 인해 더 적게 만드는 방법을 알 수 없었습니다.

  1. 이 특정 객체 / 케이스 객체 모델에 대한 JVM 객체 / 클래스 초기화가 정의되어 있지 않습니다 ( 이 Stackoverflow 스레드 참조 ).
  2. 메소드에서 리턴 된 컨텐츠 getClass.getDeclaredClasses는 정의되지 않은 순서를 갖습니다 (그리고 case object소스 코드 의 선언 과 같은 순서는 아닐 것입니다 )

이 두 가지 문제를 감안할 때, 나는 암묵적 순서를 생성하려고 포기하고 클라이언트가 명시 적으로 일종의 순서 집합 개념으로 그것을 정의하고 선언해야했습니다. 스칼라 컬렉션에는 삽입 순서 집합 구현이 없으므로 내가 할 수있는 최선의 방법은 List런타임 집합이 실제로 집합인지 확인하는 것입니다. 내가 이것을 달성하는 것을 선호했던 방법이 아닙니다.

그리고 디자인에이 두 번째 목록 / 세트 순서가 필요하면 위 valChessPiecesEnhancedDecorated예에서 추가 case object PAWN2 extends Member하고 추가하는 Decoration(PAWN2,'P2', 2)것을 잊을 수 decorationOrderedSet있었습니다. 따라서 목록이 세트 일뿐만 아니라을 확장하는 모든 케이스 오브젝트를 포함하는지 확인하기위한 런타임 검사가 있습니다 sealed trait Member. 그것은 특별한 형태의 반사 / 매크로 지옥이었습니다. 요점


에 대한 의견이나 피드백을 남겨주십시오 .


나는 지금 ScalaOlio 라이브러리 모두의 더 최신 버전을 포함 (GPL v3을)의 첫 번째 버전을 발표했다 org.scalaolio.util.Enumerationorg.scalaolio.util.EnumerationDecorated: scalaolio.org
chaotic3quilibrium

그리고 Github의 ScalaOlio 리포지토리로 직접 홉 : github.com/chaotic3quilibrium/scala-olio
chaotic3quilibrium

5
이것은 양질의 답변이며 많은 것을 취해야합니다. 감사합니다
angabriel

1
Odersky가 Dotty (미래 Scala 3.0)를 기본 열거 형으로 업그레이드하려고하는 것 같습니다. 우와 후! github.com/lampepfl/dotty/issues/1970
chaotic3quilibrium

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

물건은 게으르다. 대신 val을 사용하면리스트를 삭제할 수 있지만 이름을 반복해야합니다.

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

부정 행위가 마음에 들지 않으면 reflection 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

3
한 가지 질문 : 마지막 솔루션은 "비 게으른 케이스 객체"라고하지만이 경우 객체는 우리가 그것들을 사용할 때까지로드되지 않습니다. 왜이 솔루션을 비 게으른 것입니까?
Seb Cesbron

2
@Noel, : paste를 사용하여 봉인 된 전체 계층을 REPL에 붙여 넣습니다. 그렇지 않으면 봉인 된 기본 클래스 / 특성을 가진 단일 행이 단일 파일로 계산되어 즉시 봉인되며 다음 행에서 확장 될 수 없습니다.
Jürgen Strobel

2
@GatesDA 첫 번째 코드 스 니펫에만 버그가 없습니다 (명시 적으로 클라이언트가 값을 선언하고 정의해야하므로 두 번째 및 세 번째 솔루션 모두 마지막 주석에서 설명한 미묘한 버그가 있습니다 (클라이언트가 Currency에 액세스하는 경우) .GBP 직접 및 먼저 값 목록은 "순서가 잘못됩니다") Scala 열거 도메인을 광범위하게 살펴 보았으며이 동일한 스레드에 대한 답변에서 자세히 다루었습니다. stackoverflow.com/a/25923651/501113
chaotic3quilibrium

1
어쨌든 Java Enum과 비교 하여이 접근법의 단점 중 하나는 IDE에서 Currency <dot>를 입력하면 사용 가능한 옵션이 표시되지 않는다는 것입니다.
Ivan Balashov

1
@SebCesbron이 언급했듯이 케이스 객체는 게으르다. 따라서을 호출하면 Currency.values이전에 액세스 한 값만 가져옵니다. 그 주위에 어떤 방법이 있습니까?
Sasgorilla

27

열거에 비해 케이스 클래스를 사용하면 다음과 같은 장점이 있습니다.

  • 봉인 된 케이스 클래스를 사용하는 경우 Scala 컴파일러는 일치하는 선언에 가능한 모든 일치 항목이 포함 된 경우 일치 항목이 완전히 지정되었는지 알 수 있습니다. 열거 형을 사용하면 Scala 컴파일러가 알 수 없습니다.
  • 케이스 클래스는 이름과 ID를 지원하는 값 기반 열거보다 더 많은 필드를 자연스럽게 지원합니다.

케이스 클래스 대신 열거를 사용하면 다음과 같은 장점이 있습니다.

  • 열거 형은 일반적으로 작성하는 코드가 약간 적습니다.
  • 스칼라를 처음 접하는 사람은 다른 언어로 널리 퍼져 있기 때문에 열거를 이해하기가 조금 더 쉽습니다.

따라서 일반적으로 이름별로 간단한 상수 목록이 필요하면 열거 형을 사용하십시오. 그렇지 않으면 좀 더 복잡한 것이 필요하거나 모든 일치 항목이 지정되어 있는지 알려주는 컴파일러의 안전성을 원한다면 케이스 클래스를 사용하십시오.


15

업데이트 : 아래 코드에는 여기 에 설명 된 버그가 있습니다 . 아래 테스트 프로그램은 작동하지만 DayOfWeek 자체 이전에 DayOfWeek.Mon (예 :)을 사용하는 경우 DayOfWeek가 초기화되지 않았기 때문에 실패합니다 (내부 객체를 사용하면 외부 객체가 초기화되지 않음). val enums = Seq( DayOfWeek )메인 클래스에서 와 같이 무언가를 열거하거나 강제로 열거를 초기화하거나 chaotic3quilibrium의 수정을 사용할 수있는 경우 에도이 코드를 계속 사용할 수 있습니다 . 매크로 기반 열거 형을 기대합니다!


네가 원한다면

  • 완전하지 않은 패턴 일치에 대한 경고
  • 선택적으로 제어 할 수있는 각 열거 형 값에 할당 된 Int ID
  • 열거 된 값의 불변 인 List
  • 이름으로부터 열거 치까지의 불변의 Map
  • id에서 열거 형 값으로의 불변의 Map
  • 전체 또는 특정 열거 형 값 또는 전체 열거 형에 대한 메소드 / 데이터를 고수하는 장소
  • 정렬 된 열거 형 값 (예 : day <Wednesday인지 여부를 테스트 할 수 있음)
  • 하나의 열거 형을 확장하여 다른 것을 만들 수있는 능력

다음에 관심이있을 수 있습니다. 피드백 환영합니다.

이 구현에는 추상 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()
}

각 enum 값 (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

"day match"를 이러한 경고를 원하지 않는 "(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

List와 Maps는 변경할 수 없으므로 열거 자체를 손상시키지 않고 요소를 쉽게 제거하여 하위 집합을 만들 수 있습니다.

다음은 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

2
아마 당신을 도울 수 없습니다. 스칼라에서는 내부적으로 변경되지만 클래스를 사용하는 클래스에는 불변 인 클래스를 작성하는 것이 일반적입니다. 위의 예에서 DayOfWeek 사용자는 열거 형을 변경할 수 없습니다. 예를 들어, 사실 이후에 화요일의 ID 또는 이름을 변경하는 방법은 없습니다. 그러나 내부적 으로 돌연변이가없는 구현을 원한다면 아무것도 얻지 못했습니다. 그러나 2.11의 매크로를 기반으로 멋진 새로운 열거 형 시설을 보는 것은 놀랄 일이 아닙니다. 아이디어는 스칼라 랭에서 시작됩니다.
AmigoNico

Scala Worksheet에 이상한 오류가 발생했습니다. Value 인스턴스 중 하나를 직접 사용하면 초기화 오류가 발생합니다. 그러나 열거 형의 내용을보기 위해 .values ​​메서드를 호출하면 작동하고 값 인스턴스를 직접 사용하면 작동합니다. 초기화 오류가 무엇인지 아십니까? 그리고 호출 규칙에 관계없이 초기화가 올바른 순서로 수행되도록하는 가장 좋은 방법은 무엇입니까?
chaotic3quilibrium

@ chaotic3quilibrium : 와우! 이것을 추구해 주셔서 감사합니다. 물론 무거운 짐을 들어 준 Rex Kerr에게도 감사드립니다. 여기에 문제를 언급하고 당신이 만든 질문을 참조하십시오.
AmigoNico

"[사용 var]은 FP 세계의 경계선 필멸의 죄"입니다. 저는 의견이 보편적으로 받아 들여질 것이라고 생각하지 않습니다.
Erik Kaplun

12

여기에 자신의 값 목록을 유지하지 않고도 봉인 된 특성 / 클래스를 열거 형 값으로 사용할 수있는 멋진 간단한 라이브러리가 있습니다. 버그에 의존하지 않는 간단한 매크로에 의존합니다 knownDirectSubclasses.

https://github.com/lloydmeta/enumeratum


10

2017 년 3 월 업데이트 : Anthony Accioly의 따라 scala.Enumeration/enumPR은 마감되었습니다.

Dotty (스칼라의 차세대 컴파일러)는 1970 년의 dotty 문제를 주도 할 것입니다.Martin Odersky의 PR 1958 입니다.


참고 : 현재 (6 년 이상 지난 2016 년 8 월) 제거 제안이 있습니다. scala.Enumeration : 5352 PR

더 이상 사용되지 않음 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의 장점.

  • 실제로 작동
  • 자바 interop
  • 삭제 문제 없음
  • 열거를 정의 할 때 혼동되는 미니 DSL이 없음

단점 : 없음

이것은 Scala-JVM Scala.js및 Scala-Native ( Scala-JVM에서 지원되지 않는 Java 소스 코드 Scala.js/Scala-Native, Scala-JVM의 기존 API에서 허용되는 열거 형을 정의 할 수 없음)를 지원하는 하나의 코드베이스를 가질 수없는 문제를 해결합니다 .


위의 PR은 마감되었습니다 (기쁨 없음). 이제 2017 년이며 Dotty가 마침내 열거 형 구조를 얻는 것처럼 보입니다. 다음은 문제Martin의 PR 입니다. 병합, 병합, 병합!
Anthony Accioly

8

모든 인스턴스에서 반복하거나 필터링해야 할 경우 케이스 클래스와 열거의 또 다른 단점. 이것은 Enumeration (및 Java enum)의 기본 제공 기능이지만 케이스 클래스는 이러한 기능을 자동으로 지원하지 않습니다.

즉, "케이스 클래스를 사용하여 열거 된 값의 전체 세트 목록을 얻는 쉬운 방법은 없습니다".


5

다른 JVM 언어 (예 : Java)와의 상호 운용성을 유지하는 것이 중요하다면 최선의 선택은 Java 열거를 작성하는 것입니다. 그것들은 Scala와 Java 코드 모두에서 투명하게 작동 scala.Enumeration합니다. 피할 수 있다면 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)
}

어쩌면 누군가가 내가 한 것처럼 단순히 각 사례 클래스를 목록에 추가하는 것보다 더 나은 트릭을 만들 수 있습니다. 이것은 내가 당시에 생각해 낼 수있는 전부였습니다.


왜 두 개의 분리 된 미적용 방법이 필요한가?
Saish

@ jho 귀하의 솔루션을 그대로 사용하려고 노력했지만 컴파일되지는 않습니다. 두 번째 코드 스 니핏에는 "type V = Site"에 Site에 대한 참조가 있습니다. 컴파일 오류를 지우는 것이 무엇인지 잘 모르겠습니다. 다음으로 "추상 클래스 통화"에 빈 괄호를 제공하는 이유는 무엇입니까? 그들은 그냥 막을 수 없습니다? 마지막으로 왜 "var values ​​= ..."에 var를 사용하고 있습니까? 이것은 클라이언트가 코드의 어느 곳에서나 값에 새로운 List를 할당 할 수 있다는 것을 의미하지 않습니까? var 대신 val로 만드는 것이 훨씬 바람직하지 않습니까?
chaotic3quilibrium

2

나는 마지막 두 번이 필요했던 두 가지 옵션을왔다 갔다했습니다. 최근까지 밀봉 된 특성 / 케이스 개체 옵션을 선호했습니다.

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 value'목록을 정의해야합니다.

궁극적으로 내 의견을 바꾸게 한 것은 다음과 같은 내용이었습니다.

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

따라서 저의 선호는 저장소 및 케이스 객체 / 봉인 된 특성에서 값에 액세스 할 때 열거 형을 사용하는 것입니다.


두 번째 코드 패턴이 어떻게 바람직한 지 알 수 있습니다 (첫 번째 코드 패턴에서 두 가지 도우미 메서드 제거). 그러나 나는이 두 패턴 중에서 선택하도록 강요하지 않는 방법을 알아 냈습니다. 나는이 스레드에 게시 한 답변에서 전체 도메인을 다룹니다 : 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오버 오버 의 가장 큰 장점은 타입 클래스 패턴 일명 ad-hoc polymorphysm을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() = ...
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.