首页 文章

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

提问于
浏览
310

Scala没有类似Java的类型安全 enum . 给定一组相关常量,Scala表示这些常量的最佳方法是什么?

9 回答

  • 98

    在对Scala中的"enumerations"周围的所有选项进行了广泛的研究之后,我在另一个StackOverflow thread上发布了关于该域的更全面的概述 . 它包含了"sealed trait + case object"模式的解决方案,我解决了JVM类/对象初始化排序问题 .

  • 1

    Dotty(Scala 3)将支持原生枚举 . 检查herehere .

  • 2

    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
      }
    
  • 186

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

    为了获得最接近Java Enum 的东西(即使用合理的 toStringvalueOf 方法 - 也许你将枚举值保存到数据库中),你需要稍微修改一下 . 如果您使用过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
    
  • 52

    有很多方法可以做 .

    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
    

    它's used pretty much the same way, and doesn'甚至需要一个 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类有许多Collection方法,例如元素(Scala 2.8上的迭代器),它返回Iterator,map,flatMap,filter等 .

    这个答案基本上是我博客中this article的一个选定部分 .

  • 1

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

    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,这将更容易 .

  • 375

    您可以使用密封的抽象类而不是枚举,例如:

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

    刚刚发现enumeratum . 它's pretty amazing and equally amazing it'不是更为人所知!

  • 7

    在Scala中它非常舒服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)
    

相关问题