scala 如何建模类型安全的枚举类型?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1321745/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 01:37:21  来源:igfitidea点击:

How to model type-safe enum types?

scalaenums

提问by Jesper

Scala doesn't have type-safe enums like Java has. Given a set of related constants, what would be the best way in Scala to represent those constants?

Scala 没有enum像 Java 那样的类型安全。给定一组相关的常量,Scala 中表示这些常量的最佳方式是什么?

采纳答案by skaffman

http://www.scala-lang.org/docu/files/api/scala/Enumeration.html

http://www.scala-lang.org/docu/files/api/scala/Enumeration.html

Example use

示例使用

  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
  }

回答by oxbow_lakes

I must say that the example copied out of the Scala documentationby skaffmanabove is of limited utility in practice (you might as well use case objects).

我必须说,上面由skaffman从 Scala 文档中复制的示例在实践中的实用性有限(您不妨使用s)。case object

In order to get something most closely resembling a Java Enum(i.e. with sensible toStringand valueOfmethods -- perhaps you are persisting the enum values to a database) you need to modify it a bit. If you had used skaffman's code:

为了得到一些东西最接近类似的一个Java Enum(与理智即toStringvalueOf方法-也许你坚持枚举值到数据库),你需要稍作修改。如果您使用过skaffman的代码:

WeekDay.valueOf("Sun") //returns None
WeekDay.Tue.toString   //returns Weekday(2)

Whereas using the following declaration:

而使用以下声明:

object WeekDay extends Enumeration {
  type WeekDay = Value
  val Mon = Value("Mon")
  val Tue = Value("Tue") 
  ... etc
}

You get more sensible results:

你会得到更合理的结果:

WeekDay.valueOf("Sun") //returns Some(Sun)
WeekDay.Tue.toString   //returns Tue

回答by Daniel C. Sobral

There are many ways of doing.

有很多方法可以做。

1) Use symbols. It won't give you any type safety, though, aside from not accepting non-symbols where a symbol is expected. I'm only mentioning it here for completeness. Here's an example of usage:

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) Using class Enumeration:

2)使用类Enumeration

object Dimension extends Enumeration {
  type Dimension = Value
  val Row, Column = Value
}

or, if you need to serialize or display it:

或者,如果您需要序列化或显示它:

object Dimension extends Enumeration("Row", "Column") {
  type Dimension = Value
  val Row, Column = Value
}

This can be used like this:

这可以像这样使用:

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 /

Unfortunately, it doesn't ensure that all matches are accounted for. If I forgot to put Row or Column in the match, the Scala compiler wouldn't have warned me. So it gives me sometype safety, but not as much as can be gained.

不幸的是,它并不能确保所有匹配项都被考虑在内。如果我忘记将 Row 或 Column 放在匹配中,Scala 编译器不会警告我。所以它给了我一些类型安全,但没有获得那么多。

3) Case objects:

3) 案例对象:

sealed abstract class Dimension
case object Row extends Dimension
case object Column extends Dimension

Now, if I leave out a case on a match, the compiler will warn me:

现在,如果我在 a 上遗漏了一个案例match,编译器会警告我:

MatrixInt.scala:70: warning: match is not exhaustive!
missing combination         Column

    what match {
    ^
one warning found

It's used pretty much the same way, and doesn't even need an import:

它的使用方式几乎相同,甚至不需要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 /

You might wonder, then, why ever use an Enumeration instead of case objects. As a matter of fact, case objects do have advantages many times, such as here. The Enumeration class, though, has many Collection methods, such as elements (iterator on Scala 2.8), which returns an Iterator, map, flatMap, filter, etc.

那么,您可能想知道为什么要使用 Enumeration 而不是 case 对象。事实上,case 对象确实有很多时候的优势,比如这里。然而,Enumeration 类有许多 Collection 方法,例如元素(Scala 2.8 上的迭代器),它返回一个迭代器、映射、平面映射、过滤器等。

This answer is essentially a selected parts from this articlein my blog.

这个答案本质上是我博客中这篇文章的精选部分。

回答by Walter Chang

A slightly less verbose way of declaring named enumerations:

一种稍微不那么冗长的声明命名枚举的方式:

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

Of course the problem here is that you will need to keep the ordering of the names and vals in sync which is easier to do if name and val are declared on the same line.

当然,这里的问题是您需要保持名称和 val 的顺序同步,如果 name 和 val 在同一行上声明,这会更容易做到。

回答by ron

You can use a sealed abstract class instead of the enumeration, for example:

您可以使用密封的抽象类代替枚举,例如:

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

}

回答by practechal

just discovered enumeratum. it's pretty amazing and equally amazing it's not more well known!

刚刚发现enumeratum。它非常了不起,同样令人惊奇的是它并不为人所知!

回答by chaotic3quilibrium

After doing extensive research on all the options around "enumerations" in Scala, I posted a much more complete overview of this domain on another StackOverflow thread. It includes a solution to the "sealed trait + case object" pattern where I have solved the JVM class/object initialization ordering problem.

在对 Scala 中有关“枚举”的所有选项进行广泛研究后,我在另一个StackOverflow 线程上发布了对该域的更完整概述。它包括“密封特征 + 案例对象”模式的解决方案,我已经解决了 JVM 类/对象初始化排序问题。

回答by zeronone

Dotty (Scala 3) will have native enums supported. Check hereand here.

Dotty (Scala 3) 将支持原生枚举。检查这里这里

回答by Dmitriy Kuzkin

In Scala it is very comfortable with https://github.com/lloydmeta/enumeratum

在 Scala 中使用https://github.com/lloydmeta/enumeratum非常舒服

Project is really good with examples and documentation

项目非常好,有示例和文档

Just this example from their docs should makes you interested in

只是他们文档中的这个例子应该让你感兴趣

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)