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, AnyVal
s 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?
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.