Scala enum扩展特性

时间:2013-09-16 09:55:48

标签: java scala enums

我正在编写一个简单的解析器是Scala。

我有一个基本特征,它代表文件中的一个元素。

trait Token[T] {
    def stringValue: String
    def value: T
}

这就是我需要的 - 字符串(文本)值和解析的值(有时会是相同的字符串)。现在我希望有一组子类:

  • 保留的符号/关键字,例如classvoid等。
  • 特殊符号,例如+/等。
  • 整数文字,例如123
  • 真正的文字,例如1.23
  • 字符串文字,例如 '123'

您如何实现这样的层次结构?由于这是有限的,所以使用案例类很好..我想。但是枚举也会很棒。如何结合?


换句话说,在Scala中以更加Scal-ish的方式编写这个(下面)的最佳方法是什么?

public interface Token<T> {
    String stringValue();
    T value();
}

public enum ReservedSymbol implements Token<ReservedSymbol> {
    CLASS('class'), VOID('void');

    private String val;
    private ReservedSymbol(String val) { this.val = val; }

    public String stringValue() { return val; }
    public ReservedSymbol value() { return this; }
}


public class IntegerLiteral implements Token<Integer> {       
    private Integer val;
    public IntegerLiteral(String val) { this.val = Integer.valueOf(val); }

    public String stringValue() { return val.toString(); }
    public Integer value() { return val; }
}

2 个答案:

答案 0 :(得分:5)

在Scala中构建此类层次结构时,请尝试应用以下原则:

  1. 绘制所需的类层次结构。设计它的方式是只实例化叶子节点,内部节点是抽象的。
  2. 将内部节点实现为特征
  3. 将叶节点实现为案例类
  4. 原因是,case类自动添加了很多有用的魔法(toString,unapply,serialize,equals等)。但必要的代码是在远离时生成的,与案例类之间的继承不兼容(例如equals would not work properly)。

    通常,没有参数的叶子类型通常被建模为case object,而带参数的叶子类型被建模为case class

    当您需要实例化类型树的内部节点时,只需添加一个人工叶子并将其实现为案例类/案例对象。

    您也可以在Scala中使用Enumerations,但通常案例类更实用。当您需要将给定字符串转换为相应的枚举时,枚举通常是一个不错的选择。您可以使用Enumeration.withName(String)

    执行此操作

    在Alexey Romanov的回答中,您可以看到如何将此原则应用于具有一个根节点和三个叶节点的类型树。

    1. Token(内部节点=&gt;特质)

      1.1。 ClassSymbol(没有参数的叶节点=&gt;案例对象)

      1.2。 VoidSymbol(没有参数的叶节点=&gt;案例对象)

      1.3。 IntegerLiteral。 (带参数的叶节点=&gt;案例类)

    2. 使用枚举和案例类的情况示例:

      trait Token[T]{
        def stringValue: String 
        def value: T
      }
      
      object ReservedSymbolEnum extends Enumeration {
        type ReservedSymbolEnum = Value
        val `class`, `void` = Value
            val NullValue = Value("null") // Alternative without quoting
      }
      
      case class ReservedSymbol(override val stringValue: String)extends Token[ReservedSymbolEnum.ReservedSymbolEnum] {
        def value = ReservedSymbolEnum.withName(stringValue)
      }
      
      case class StringLiteral(override val stringValue: String) extends Token[String] {
        override def value = stringValue
      }
      
      case class IntegerLitaral(override val stringValue: String) extends Token[Int] {
        override def value = stringValue.toInt
      }
      

      一些用法示例:

      scala> def `void`=ReservedSymbol("void")
      void: ReservedSymbol
      
      scala> `void`.value
      res1: ReservedSymbolEnum.Value = void
      
      scala> def `42`=IntegerLiteral("42")
      42: IntegerLitaral
      
      scala> `42`.value
      res2: Int = 42
      

答案 1 :(得分:3)

sealed trait Token[+T] { // sealed means it only can be extended in this file
  def stringValue: String
  def value: T
}

// cast can be avoided if you are happy with extending Token[ReservedSymbol]
// as in the Java example
// class instead of trait so that it can have a constructor argument
sealed class ReservedSymbol[+T <: ReservedSymbol[T]](val stringValue: String) extends Token[T] {
  def value = this.asInstanceOf[T] 
}

// no body necessary
case object ClassSymbol extends ReservedSymbol[ClassSymbol]("class")
case object VoidSymbol extends ReservedSymbol[VoidSymbol]("void")

case class IntegerLiteral(val: Int) extends Token[Int] { ... }