scalanumbersdecimalvalue-class

Using Scala value classes to define new numeric type


When I first heard of value classes, I thought -- finally! Now I can define my own numeric types sans object allocations! But it turned out harder than I thought.

I want to define my own Decimal type, either Dec64 (http://dec64.com/) or long-backed decimal for fast monetary calculations. However, AnyVals can't extend Numeric, as Numeric is not a universal trait. I tried to follow up the Scala code for Double, but it is quite complicated, with AnyValCompanion, FractionalProxy and lots of private[scala] marked code, with very helpful comments like Should not be extended in user code.

So, how do I properly define my own numeric value type that can play well together with other Scala numbers?


Solution

  • Numeric is a type class, so your class wouldn't extend it. Instead, you'd create an instance of the type class for your type. In the example below I use Int for simplicity.

    final class MyNum(val i: Int) extends AnyVal
    object MyNum {
      implicit val numeric: Numeric[MyNum] = new Numeric[MyNum] {
        override def plus(x: MyNum, y: MyNum): MyNum = new MyNum(x.i + y.i)
        override def minus(x: MyNum, y: MyNum): MyNum = new MyNum(x.i - y.i)
        override def times(x: MyNum, y: MyNum): MyNum = new MyNum(x.i * y.i)
        override def negate(x: MyNum): MyNum = new MyNum(-x.i)
        override def fromInt(x: Int): MyNum = new MyNum(x)
        override def toInt(x: MyNum): Int = x.i
        override def toLong(x: MyNum): Long = x.i.toLong
        override def toFloat(x: MyNum): Float = x.i.toFloat
        override def toDouble(x: MyNum): Double = x.i.toDouble
        override def compare(x: MyNum, y: MyNum): Int = x.i.compare(y.i)
      }
    }
    

    If you aren't familiar with type classes, I would recommend Learn You A Haskell For Great Good, particularly its section on type classes. It's just an excellent book anyway, I highly recommend reading the entire thing to fully understand where these ideas come from.