형식 안전 열거 형 형식을 모델링하는 방법은 무엇입니까?


311

스칼라에는 enumJava와 같은 유형 안전 기능이 없습니다 . 관련 상수 세트가 주어지면 스칼라에서 이러한 상수를 나타내는 가장 좋은 방법은 무엇입니까?


2
왜 자바 열거 형을 사용하지 않습니까? 이것은 여전히 ​​일반 자바를 선호하는 몇 가지 중 하나입니다.
Max

1
scala 열거 및 대안에 대한 작은 개요를 작성했습니다. 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
진심으로, 응용 프로그램을 사용해서는 안됩니다. 수정되지 않았습니다. Schildmeijer가 언급 한 문제가없는 새로운 클래스 인 App이 도입되었습니다. "object foo extends App {...}"와 마찬가지로 args 변수를 통해 명령 줄 인수에 즉시 액세스 할 수 있습니다.
AmigoNico

scala.Enumeration (위의 "object WeekDay"코드 샘플에서 사용중인 것)은 철저한 패턴 일치를 제공하지 않습니다. 나는 현재 스칼라에서 사용되는 모든 다른 열거 패턴을 연구 하고이 StackOverflow 답변 (스칼라 .Enumeration과 "밀봉 된 특성 + 사례 개체"패턴 : stackoverflow)
chaotic3quilibrium

377

나는 예라고한다 스칼라 문서 밖으로 복사 에 의해 skaffman은 위의 연습에 제한 유틸리티입니다 (당신 수도뿐만 아니라 사용 case object의).

Java와 가장 유사한 것을 얻으려면 Enum(예를 들어 현명 toString하고 valueOf메소드가있는-아마도 enum 값을 데이터베이스에 유지하는 경우) 약간 수정해야합니다. 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. valueOf 메소드가 이제 종료되었습니다 :-(
greenoldman

36
@macias valueOf의 replacement is withName는 옵션을 반환하지 않으며 일치하지 않으면 NSE를 throw합니다. 뭐야!
Bluu

6
@Bluu 스스로 valueOf를 추가 할 수 있습니다 : def valueOf (name : String) = WeekDay.values.find (_. toString == name) 옵션을 갖도록
centr

@ centr a를 만들고 Map[Weekday.Weekday, Long]값을 추가 하려고 Mon하면 컴파일러에서 잘못된 유형 오류가 발생합니다. 예상 요일. 평일에 값을 찾았습니까? 왜 이런 일이 발생합니까?
Sohaib

@Sohaib Map [Weekday.Value, Long]이어야합니다.
centr

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 /

그렇다면 왜 케이스 객체 대신 Enumeration을 사용하는지 궁금 할 것입니다. 실제로 케이스 객체는 여기와 같이 여러 번 장점이 있습니다. 그러나 Enumeration 클래스에는 반복자,지도, flatMap, 필터 등을 반환하는 요소 (Scala 2.8의 반복자)와 같은 많은 Collection 메소드가 있습니다.

이 답변은 본질적으로 내 블로그 의이 기사 에서 선택한 부분입니다 .


"... 기호가 예상되는 심볼이 아닌 기호는 허용하지 않습니다"> Symbol인스턴스에 공백이나 특수 문자를 사용할 수 없음 을 의미합니다 . Symbol수업을 처음 접했을 때 대부분의 사람들은 아마도 그렇게 생각하지만 실제로는 틀립니다. Symbol("foo !% bar -* baz")컴파일하고 완벽하게 실행합니다. 즉 당신은 완벽하게 만들 수 있습니다 Symbol포장 인스턴스 모든 문자열을 (그냥 "하나의 혼수 상태"문법 설탕 함께 할 수 없습니다). Symbol보장 하는 유일한 것은 주어진 심볼의 독창성이므로 비교하고 비교하는 것이 조금 더 빠릅니다.
Régis Jean-Gilles

@ RégisJean-Gilles 아니요, String예를 들어 Symbol매개 변수에 인수 로을 전달할 수 없습니다 .
Daniel C. Sobral

예, 그 부분을 이해했지만 String기본적으로 문자열을 감싸는 래퍼 인 다른 클래스로 바꾸면 양방향으로 자유롭게 변환 할 수 있습니다 (의 경우와 같이 Symbol). "유형 안전을 제공하지 않습니다"라고 말할 때 이것이 의미하는 것 같습니다. OP가 유형 안전 솔루션을 명시 적으로 요구 한 것을 감안할 때 명확하지 않았습니다. 나는 글을 쓰는 시점에 그것들이 전혀 열거 형이 아니기 때문에 유형이 안전 하지 않을 뿐만 아니라 Symbol 전달 된 인수에 특별한 문자가 없다는 것을 보증하지 않는다는 것을 알았는지 확신 하지 못했습니다.
Régis Jean-Gilles

1
자세히 설명하기 위해 "심볼이 예상되는 심볼이 아닌 심볼을 허용하지 않음"이라고 말하면 "심볼의 인스턴스가 아닌 값을 허용하지 않음"(확실히 사실임) 또는 "이 아닌 값을 허용하지 않음"으로 읽을 수 있습니다. 일반 식별자와 같은 문자열, 일명 '상징' "(사실이 아니다, 그리고 꽤 많은 사람이 때문에 첫 만남은 특별한 불구하고 있다는 사실에, 우리는 스칼라 기호가 발생할 처음이 오해 인 'foo표기 하지 배제가 비 식별자 문자열). 이것은 미래의 독자를 위해 없애고 싶었던이 오해입니다.
Régis Jean-Gilles

@ RégisJean-Gilles 나는 전자를 의미했다. 정적 타이핑에 익숙한 사람에게는 분명히 해당됩니다. 당시에는 정적 및 "동적"타이핑의 상대적인 장점에 대해 많은 토론이 있었고 스칼라에 관심이있는 많은 사람들이 역동적 인 타이핑 배경에서 왔기 때문에 말할 필요 도 없다고 생각했습니다 . 나는 요즘 그 발언을 할 생각조차하지 않을 것입니다. 개인적으로 스칼라의 상징은 추악하고 중복 적이며 결코 사용하지 않는다고 생각합니다. 나는 당신의 마지막 의견을 찬성하고 있습니다. 그것이 좋은 지적이기 때문입니다.
Daniel C. Sobral

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

물론 여기서 문제는 이름과 val의 순서를 동기화하여 유지해야한다는 것인데, name과 val이 같은 줄에 선언되어 있으면 더 쉽습니다.


11
이것은 언뜻보기에는 깨끗해 보이지만 유지 보수가 두 목록의 순서를 동기화하도록 요구해야한다는 단점이 있습니다. 요일 예의 경우에는 가능성이 나타나지 않습니다. 그러나 일반적으로 새 값을 삽입하거나 하나를 삭제하고 두 목록을 동기화하지 않을 수 있으며이 경우 미묘한 버그가 발생할 수 있습니다.
브렌트 파우스트

1
이전 의견에 따르면 두 가지 목록이 자동으로 동기화되지 않을 수 있습니다. 현재의 작은 예에서는 문제가 아니지만 수십에서 수백 개와 같이 더 많은 멤버가있는 경우 두 목록이 자동으로 동기화되지 않을 가능성이 상당히 높습니다. 또한 scala.Enumeration은 Scala의 컴파일 타임 전체 패턴 일치 경고 / 오류의 이점을 얻을 수 없습니다. 두 목록이 동기화 상태를 유지하도록 런타임 검사를 수행하는 솔루션이 포함 된 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))
  }

}

케이스 오브젝트가있는 봉인 된 특성도 가능합니다.
Ashalynd

2
"밀봉 된 특성 + 사례 개체"패턴에는 StackOverflow 답변에서 자세히 설명하는 문제가 있습니다. 그러나,이 또한 스레드에 덮여 패턴에 관련된 모든 문제를 해결하는 방법을 알아낼했다 stackoverflow.com/a/25923651/501113
chaotic3quilibrium

7

방금 enumeratum을 발견했습니다 . 그것은 꽤 놀랍고 똑같이 놀랍습니다. 더 잘 알려져 있지 않습니다!


2

스칼라의 "enumerations"에 관한 모든 옵션에 대한 광범위한 연구를 한 후,이 도메인에 대한 훨씬 더 완전한 개요를 다른 StackOverflow 스레드 에 게시했습니다 . 여기에는 JVM 클래스 / 개체 초기화 순서 문제를 해결 한 "밀봉 된 특성 + 사례 개체"패턴에 대한 솔루션이 포함되어 있습니다.



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.