SkyWalker
SkyWalker

Reputation: 14307

How to dynamically create an Enum type in Scala?

I have a basic enum type Currency that will include all major currencies traded e.g. EUR, USD, JPY, etc. This code I can write or generate one time. However, I'd also like to have strong enum type for all currency pair combinations e.g. EURCHF, USDCHF, etc. Is there any provision in Scala that would allow me to build such a derived enum type dynamically? I could also do it with some script generator from outside ... but I wonder whether it would be possible.

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 using the accepted answer as reference this was my solution implementation:

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

and then I can do things like:

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)

Upvotes: 1

Views: 1293

Answers (1)

Kolmar
Kolmar

Reputation: 14224

There is no magic in Scala's Enumeration. The call to the Value function inside simply does some modifications to Enumeration's internal mutable structures. So you just have to call Value for each pair of currencies. The following code will work:

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

It's not very comfortable to work with though. You can access the values only through withName or values functions.

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

But it's possible to extend this definition, for example, to allow retrieving CcyPair.Value by a pair of Ccy.Values, or to allow access by object fields with Dynamic, or to provide other facilities you may need:

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

This definition is a bit more useful:

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

Upvotes: 5

Related Questions