如何在Scala中动态创建枚举类型?

时间:2017-11-06 09:58:09

标签: scala enums

我有一个基本的枚举类型货币,其中包括交易的所有主要货币,例如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)

1 个答案:

答案 0 :(得分:3)

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

但是,可以扩展此定义,例如,允许通过一对CcyPair.Value检索Ccy.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