【问题标题】:How to dynamically create an Enum type in Scala?如何在 Scala 中动态创建 Enum 类型?
【发布时间】:2017-11-07 17:34:48
【问题描述】:

我有一个基本的枚举类型 Currency,它将包括所有交易的主要货币,例如EURUSDJPY等。这段代码我可以编写或生成一次。但是,我还想为所有货币对组合使用强枚举类型,例如EURCHFUSDCHF 等。Scala 中是否有任何规定可以让我动态构建这样的派生枚举类型?我也可以用外部的一些脚本生成器来做......但我想知道这是否可能。

object Ccy extends Enumeration {
   type Type = Value
   val USD = Value("USD")
   val CHF = Value("CHF")
   val EUR = Value("EUR")
   val GBP = Value("GBP")
   val JPY = Value("JPY")
}

object CcyPair extends Enumeration {
   type Type = Value
   // ??? Ccy.values.toSeq.combinations(2) ...   
}

UPDATE 使用接受的答案作为参考,这是我的解决方案实现:

import scala.language.dynamics

object CcyPair extends Enumeration with Dynamic {
  type Type = Value
  /* 
   * contains all currency combinations including the symmetric AB and BA
   */
  private val byCcy: Map[(Ccy.Value, Ccy.Value), Value] =
    Ccy.values.toSeq.combinations(2).map { case Seq(c1, c2) =>
      Seq(
        (c1, c2) -> Value(c1.toString + c2.toString),
        (c2, c1) -> Value(c2.toString + c1.toString)
      )
    }.flatten.toMap
  /** 
   * reverse lookup to find currencies by currency pair, needed to find
   * the base and risk components.
   */ 
  private val revByCcy = byCcy.toSeq.map { case (((ccyRisk, ccyBase), ccyPair)) =>
    ccyPair -> (ccyRisk, ccyBase)
  }.toMap

  def apply(ccy1: Ccy.Value, ccy2: Ccy.Value): Value = {
    assert(ccy1 != ccy2, "currencies should be different")
    byCcy((ccy1, ccy2))
  }

  implicit class DecoratedCcyPair(ccyPair: CcyPair.Type) {
    def base: Ccy.Type = {
      revByCcy(ccyPair)._1
    }

    def risk: Ccy.Type = {
      revByCcy(ccyPair)._2
    }

    def name: String = ccyPair.toString()
  }

  def selectDynamic(ccyPair: String): Value = withName(ccyPair)
}

然后我可以执行以下操作:

val ccyPair = CcyPair.EURUSD
// or
val ccyPair = CcyPair(Ccy.EUR, Ccy.USD) 

// and then do
println(ccyPair.name)
// and extract their parts like:
// print the base currency of the pair i.e. EUR
println(CcyPair.EURUSD.base) 
// print the risk currency of the pair i.e. USD
println(CcyPair.EURUSD.risk)

【问题讨论】:

    标签: scala enums


    【解决方案1】:

    Scala 的Enumeration 没有魔法。内部对Value 函数的调用只是对Enumeration 的内部可变结构进行了一些修改。因此,您只需为每对货币致电Value。以下代码将起作用:

    object CcyPair1 extends Enumeration {
      Ccy.values.toSeq.combinations(2).foreach {
        case Seq(c1, c2) =>
          Value(c1.toString + c2.toString)
      }
    }
    

    虽然使用起来不太舒服。您只能通过withNamevalues 函数访问这些值。

    scala> CcyPair1.withName("USDEUR")
    res20: CcyPair1.Value = USDEUR
    

    但可以扩展此定义,例如,允许通过一对Ccy.Values 检索CcyPair.Value,或者允许通过带有Dynamic 的对象字段进行访问,或者提供您可能需要的其他功能:

    import scala.language.dynamics
    
    object CcyPair2 extends Enumeration with Dynamic {
      val byCcy: Map[(Ccy.Value, Ccy.Value), Value] =
        Ccy.values.toSeq.combinations(2).map {
          case Seq(c1, c2) =>
            (c1, c2) -> Value(c1.toString + c2.toString)
        }.toMap
    
      def forCcy(ccy1: Ccy.Value, ccy2: Ccy.Value): Value = {
        assert(ccy1 != ccy2, "currencies should be different")
        if (ccy1 < ccy2) byCcy((ccy1, ccy2))
        else byCcy((ccy2, ccy1))
      }
    
      def selectDynamic(pairName: String): Value =
        withName(pairName)
    }
    

    这个定义更有用一点:

    scala> CcyPair2.forCcy(Ccy.USD, Ccy.EUR)
    res2: CcyPair2.Value = USDEUR
    
    scala> CcyPair2.forCcy(Ccy.EUR, Ccy.USD)
    res3: CcyPair2.Value = USDEUR
    
    scala> CcyPair2.USDCHF
    res4: CcyPair2.Value = USDCHF
    

    【讨论】:

    • 等一下......在你的回答中你做了CcyPair2.USDCHF 并且被神奇地定义但对我来说不存在......
    • @GiovanniAzua 是的,这是因为CcyPair2 的定义扩展了Dynamic,并覆盖了selectDynamic。有关更多说明,请参阅stackoverflow.com/questions/15799811/…。在这里使用它可能不是一个好主意,因为它会导致运行时错误而不是编译时,我只是举个例子。
    • 啊,是的,很抱歉,我错过了扩展 Dynamic 和所需的导入。
    • 我认为这种动态方法是一个很好的权衡。我没有可以理解的编译时检查存在,但是通过良好的测试覆盖率,所有运行时错误都会被捕获......至少它不会像测试等效的基于 String 的解决方案那样复杂且容易出错
    猜你喜欢
    • 2011-07-15
    • 1970-01-01
    • 1970-01-01
    • 2010-11-30
    • 2022-01-14
    • 1970-01-01
    • 2021-05-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多