1

I'm new to scala and trying to understand the right way to think about subtypes, so here's a simple example.

Let's say I want to make a function truncation() that takes a number and rounds it down to a few decimals places and returns the result. I might go about this as,

def truncation(number:Double, level:Int)={
  math.floor(number * math.pow(10,level)) / math.pow(10,level)
}

truncation(1.2345, 2)
res0: Double = 1.23

But I probably also want this function to work with other numeric types besides Double, such as Float.

So how should I think about generalizing this function to work well with multiple types?

I'm thinking I should be using generic types such as

def truncation [A](number:A, level:Int):A={
  math.floor(number * math.pow(10,level)) / math.pow(10,level)
}

but this doesn't compile. In the case of just two types, I see that the Either type is a good option. But in the more general case,maybe I'll want to be able to handle Ints as well, and have different implementations that match on the type of the input object.

What's the best way to be thinking about this? Thanks for your help.

Community
  • 1
  • 1
keegan
  • 2,892
  • 1
  • 17
  • 20
  • 1
    This sounds like a situation for Scala Type Classes. You should search for that and read tutorials on it. – cmbaxter Apr 02 '15 at 14:29
  • 1
    Something like this link: http://danielwestheide.com/blog/2013/02/06/the-neophytes-guide-to-scala-part-12-type-classes.html – cmbaxter Apr 02 '15 at 14:30
  • thanks @cmbaxter! I actually just stumbled on this one. I'll read through this and come up with an answer – keegan Apr 02 '15 at 14:33

1 Answers1

3

For a generic that you want to constrain to numeric types, you can use Numeric:

def truncation[T](number: T, level:Int)(implicit n: Numeric[T]) = {
    import math._
    val doubleValue = n.toDouble(number)
    floor(doubleValue * pow(10,level)) / pow(10,level)
}

Or equivalently:

def truncation[T : Numeric](number: T, level:Int) = {
    import math._
    val doubleValue = implicitly[Numeric[T]].toDouble(number)
    floor(doubleValue * pow(10,level)) / pow(10,level)
}

These will work for Ints, Doubles, Floats, and other numeric types.

The first example uses an implicit parameter, which you can read about here. The second version uses a context bound, which you can read about here together with the implicitly operator, which you can read about here. Finally, read the documentation of Numeric here to see all the available methods.

Note that the versions above both return Double. If you want them to return T (whatever the input type is), you can try:

def truncation[T : Numeric](number: T, level:Int): T = implicitly[Numeric[T]] match {
    case n:Fractional[T] =>
        val tenPow = n.fromInt(math.pow(10, level).toInt)
        n.div(n.fromInt(n.toInt(n.times(number, tenPow))), tenPow)
    case n:Integral[T] => number
}
Community
  • 1
  • 1
Ben Reich
  • 16,222
  • 2
  • 38
  • 59
  • thanks! I was just reading about `Numeric` and was unsure how to utilize it. So we have to convert with `toDouble` so that `doubleValue` and the result of `pow` are of the same type for `*`? – keegan Apr 02 '15 at 16:09
  • 1
    Right. `math.pow` returns `Double`, so we need a `Double` to multiply it with. Furthermore, `floor` expects a double, so the input needs to be a `double` as well. – Ben Reich Apr 02 '15 at 16:12