0

I have a base trait:

trait NumberFactory {
   def createNumber(): Double
}

I can implement this trait by saying:

class RandomNumberFactory extends NumberFactory {
    def createNumber() = Random.nextDouble()
}

It would be nice to specialise the NumberFactory to have a special IntFactory.

class IntFactory extends NumberFactory {
    def createNumber(): Int = 42 // returns an integer
}

In my opinion this would make sense, since every integer is a double in some sense (every whole number is a real number). However, Int and Double seem to be invariant. What would be the solution here?

In my real example NumberFactory contains multiple variables and it has two implementations: one that contains the variables as integers and one that contains them as real numbers. If I have a reference to a NumberFactory it would be fine to have a real number returned, but in case of an IntFactory I need an integer.

David Frank
  • 5,918
  • 8
  • 28
  • 43
  • 2
    It is true that every integer is a real number but not every real number is a `Double`. For example, 1/10 can not be represented as a `Double`, but more importantly, 9007199254740993, a whole number, cannot be represented as a `Double`, either. – Jörg W Mittag Nov 27 '14 at 00:01

3 Answers3

3

Inheritance in Scala (and in almost any other language) means subtyping relationship: if A extends B, then everywhere where you can use B, you can use A. Every operation which can be applied to B you can also apply to A. For data structures this means that child contents are a superset of parent contents; operations can usually be dispatched virtually to allow specializing.

However, all of this does not make sense for Double/Int. While Double does cover the whole range of Int (because its mantissa is 53 bits, which is almost twice as long as the whole Int), their internal representation is completely different and neither is the superset of the other.

This would probably be unimportant if numerical types used virtual dispatch. After all you could override +/-/... for Int to behave differently, using Int internal structure, couldn't you? However, this implies that every single one numeric value should carry a piece of metadata with it (a virtual table and a lot of JVM-related stuff), which will be larger than the value itself! Moreover, every single numerical operation should go through the virtual table instead of a simple assembly operation.

Another problem with virtual dispatch on numeric types is this:

def divide(x: Double, y: Double): Double {
  x / y
}

let (a: Int, b: Int) = (1, 2)
assert(divide(a, b) == 0.5)

Naturally, you would expect that divide() returned 0.5, right? However, with virtual dispatch it would return 0, because actual division is performed on Ints with their own division method! And what happens if we mix Double and Int?

divide(1: Int, 2: Double) == ?

Which rules should be used here? Maybe some kind of automatic conversion should be applied?

You see, making Int a subtype of Double brings a lot of questions on how to do it properly and efficiently. Other approaches of unifying numerical types and operations on them usually do not base on classes and inheritance, but JVM is rather rigid in this regard.

What you really need depends on your actual program. The simplest way is just to return literal 42 from Double method (it will be used as Double automatically):

class IntFactory extends NumberFactory {
    def createNumber(): Double = 42
}

Suggestion to use a type parameter is also valid for other use cases.

Vladimir Matveev
  • 120,085
  • 34
  • 287
  • 296
  • This is a very useful explanation, thank you for your effort. If nobody comes up with a better solution, I will accept it in a few days. You deserve a +1 anyway. – David Frank Nov 27 '14 at 13:43
  • What I also tried is to use a view bound, like this: `abstract class NumberFactory[T <% Double] { def createNumber(): T }` but for some reason `val numberFactory: NumberFactory[_] = …; val x: Double = numberFactory.createNumber()` gives me a compile error. Apparently view bounds are not propagated to return types. – David Frank Nov 27 '14 at 13:47
  • Well, view bounds are just a way to ask for an implicit conversion, and, first there is no implicit conversion from `Int` to `Double`, such coercions are just handled by the compiler directly; second, such conversion, if there was one, would only be visible *inside* the class, not where you call the method. And finally, they are going to be deprecated anyway :) – Vladimir Matveev Nov 27 '14 at 14:08
0

I would use parametric polymorphism, so that the type becomes generic over variants, and still it leverages type safety. Example code:

import scala.util.Random

trait NumberFactory[T] {
  def createNumber(): T
}
class RandomNumberFactory extends NumberFactory[Double] {
  def createNumber() = Random.nextDouble()
}
class IntFactory extends NumberFactory[Int] {
  def createNumber() = 42
}

val randomNumberFactory : NumberFactory[Double] = new RandomNumberFactory()
val intFactory : NumberFactory[Int] = new IntFactory()
val doubleValue1 :Double = randomNumberFactory.createNumber
val intValue :Int = intFactory.createNumber
val doubleValue2 :Double = intFactory.createNumber      // Scala takes care of implicit conversion
val intValue2 :Int = randomNumberFactory.createNumber() // Compilation error; not every real number is integer
suztomo
  • 5,114
  • 2
  • 20
  • 21
  • I experimented with this approach. The problem is that most of the time all I've got is a NumberFactory[_] which then can't be used for anything (without explicit casting). – David Frank Nov 27 '14 at 13:41
  • Right; otherwise there's possibility that you'll get Double where you're expecting int. From Vladimir Matveev's answer, it looks returning Double satisfies your need. – suztomo Nov 27 '14 at 20:21
0

Here is a solution using the Numeric type class (same code than Suztomo described):

trait NumberFactory[A] {
  def createNumber(): A
}

class RandomDoubleFactory extends NumberFactory[Double] {
  def createNumber(): Double = scala.util.Random.nextDouble()
}

class IntFactory extends NumberFactory[Int] {
  def createNumber(): Int = 42
}

// example use case
import scala.math.Numeric
import scala.math.Numeric.Implicits._

def compute[A: Numeric](factory: NumberFactory[A], x: A): A =
  x * factory.createNumber() + x

compute(new RandomDoubleFactory, 42.3d)
compute(new IntFactory, 42)

There are some limitations on the Numeric type class though, for example there is no division. The Numeric typeclass of spire seem to have a div, I have not digged much.

The [A: Numeric] part is a context bound. Numeric instances are already provided by the scala library, so you donc have to implement them.

The Implicits import allows you to have common operators on A (like *, +, etc). Otherwise you would have to write ugly things like this: implicitly[A].plus(x, factory.createNumber()).

Community
  • 1
  • 1
Dimitri
  • 1,786
  • 14
  • 22