Friday, October 16, 2015

The Currency class: a case study of abstract types in Scala

object Example {

  object Converter {
    var exchangeRate = Map(
      "USD" -> Map("USD" -> 1.0, "EUR" -> 0.7596,
        "JPY" -> 1.211, "CHF" -> 1.223),
      "EUR" -> Map("USD" -> 1.316, "EUR" -> 1.0,
        "JPY" -> 1.594, "CHF" -> 1.623),
      "JPY" -> Map("USD" -> 0.8257, "EUR" -> 0.6272,
        "JPY" -> 1.0, "CHF" -> 1.018),
      "CHF" -> Map("USD" -> 0.8108, "EUR" -> 0.6160,
        "JPY" -> 0.982, "CHF" -> 1.0))
  }

  abstract class CurrencyZone {
    type Currency <: AbstractCurrency
    val currencyUnit: Currency

    def make(x: Long): Currency

    abstract class AbstractCurrency {
      val amount: Long

      def designation: String

      def +(that: Currency): Currency = {
        make(this.amount + that.amount)
      }

      def *(x: Double): Currency = {
        make((this.amount * x).toLong)
      }

      def -(that: Currency) = {
        make(this.amount - that.amount)
      }

      def /(that: Double) = make((this.amount / that).toLong)

      def from(other: CurrencyZone#AbstractCurrency): Currency = {
        val e = Converter.exchangeRate(other.designation)(designation)
        make(math.round(other.amount.toDouble * e))
      }

      override def toString = {
        val dollars = amount / currencyUnit.amount
        val cents = amount % currencyUnit.amount
        "%d %s %d".format(dollars, designation, cents)
      }
    }


  }


  object US extends CurrencyZone {

    abstract class Dollar extends AbstractCurrency {
      def designation = "USD"
    }

    type Currency = Dollar

    def make(x: Long) = new Dollar {
      val amount = x
    }

    val currencyUnit = make(100)
  }

  object EU extends CurrencyZone {
    abstract class Euro extends AbstractCurrency {
      def designation = "EUR"
    }
    override type Currency = Euro

    override def make(x: Long): EU.Currency = new Euro {
      override val amount: Long = x
    }

    override val currencyUnit: EU.Currency = make(100)
  }

}


object Test {

  import Example._

  val dollar = US.currencyUnit
  val euro = EU.currencyUnit
  val x = US.make(120)
  x + US.make(110) // $2.30
  x * 2.0 // $2.40
  val y = EU.make(120)
  y + EU.make(110) // €2.30
  y * 2.0 // €2.40
  US.make(100) + x.from(EU.make(100)) // $2.32
  dollar + dollar.from(euro)
  US.make(200) + dollar.from(EU.make(200)) // around $4.64
}

0 comments: