1

I have the below abstract class and its two subclasses:

abstract class BoundedNumber(val lowerBound: Double,
                             val upperBound: Double,
                             val value: Double) {
  require(value >= lowerBound && value <= upperBound)
}

final case class Percentage(override val value: Double)
  extends BoundedNumber(0, 100, value)

final case class Probability(override val value: Double)
  extends BoundedNumber(0, 1, value)

Can I somehow implement a "generic" addition in BoundedNumber even though it's abstract and cannot be instantiated?

abstract class BoundedNumber(val lowerBound: Double,
                             val upperBound: Double,
                             val value: Double) {
  def +(that: BoundedNumber): BoundedNumber = {
    require(this.getClass == that.getClass)
    // This of course won't compile:
    new BoundedNumber(lowerBound, upperBound, value + that.value)
  }
}

Or am I bound to (pun intended) implement addition in both subclasses thus duplicating code?

bugfoot
  • 667
  • 7
  • 20
  • What is the main concern here? Are you trying to avoid adding code to the subclasses, or are you just trying to avoid having the `add` logic in two places? Is the solution allowed to add code to the subclasses? – Tim Jul 03 '18 at 07:33
  • There are more than 2 subclasses and I would like to avoid adding duplicate-ish code anywhere. – bugfoot Jul 03 '18 at 15:42

3 Answers3

3

You cannot instantiate abstract class, However you can specify an abstract method in BoundedNumber that create new instance with required updated value.

  abstract class BoundedNumber(val lowerBound: Double,
                               val upperBound: Double,
                               val value: Double) {
    require(value >= lowerBound && value <= upperBound)

    def copy(value: Double): BoundedNumber

    def +(that: BoundedNumber): BoundedNumber = {
      require(this.getClass == that.getClass)
      that.copy(value + that.value)
    }
  }

  final case class Percentage(override val value: Double) extends BoundedNumber(0, 100, value) {
    override def copy(value: Double): BoundedNumber =  Percentage(value)
  }

  final case class Probability(override val value: Double) extends BoundedNumber(0, 1, value){
    override def copy(value: Double): BoundedNumber = Probability(value)
  }
Ra Ka
  • 2,995
  • 3
  • 23
  • 31
  • 3
    You can also set both `copy` and `+` to return/take instances of the subclass (and get rid of the `require(this.getClass == that.getClass)`, which is... not a great way of enforcing type safety) by F-bounded polymorphism (https://twitter.github.io/scala_school/advanced-types.html#fbounded) – Astrid Jul 03 '18 at 06:34
  • This answer coupled with @Astrid's comment to make the code more type safe I can solve my original program, but code "duplication" (of `copy`) remains, which I guess is unavoidable with abstract class methods that return concrete subclass instances. – bugfoot Jul 07 '18 at 06:54
1

If your goal is compile-time type safety in the sense that both operands of + must be of the same concrete type and that + also returns the same concrete type, you can declare an abstract type and constructor which will be implemented by every concrete subclass. + can then be defined in the abstract class:

abstract class BoundedNumber(val lowerBound: Double, val upperBound: Double, val value: Double) {
    require(value >= lowerBound && value <= upperBound)
    type Self <: BoundedNumber
    def make(value: Double): Self

    def +(that: Self): Self = make(value + that.value)
}

final case class Percentage(override val value: Double) extends BoundedNumber(0, 100, value) {
    type Self = Percentage
    def make(value: Double): Self = Percentage(value)
}

final case class Probability(override val value: Double) extends BoundedNumber(0, 1, value) {
    type Self = Probability
    def make(value: Double): Self = Probability(value)
}

Now the compiler will correctly infer that the parameter for + on Percentage must be of type Percentage and that the result will be of type Percentage.

lslah
  • 556
  • 8
  • 17
-1

Create a concrete class inside the abstract class and return that:

abstract class BoundedNumber(val lowerBound: Double,
                             val upperBound: Double,
                             val value: Double) {

  class ConcreteBoundedNumber(lowerBound: Double,
                              upperBound: Double,
                              value: Double) extends BoundedNumber(lowerBound, upperBound, value)

  def +(that: BoundedNumber): BoundedNumber = {
    require(this.getClass == that.getClass)

    new ConcreteBoundedNumber(lowerBound, upperBound, value + that.value)
  }
}

This gives a generic implementation of + without adding any code to the subclasses.

Tim
  • 26,753
  • 2
  • 16
  • 29
  • Please give an explanation for the downvote, since I clearly answered the question correctly. – Tim Jul 03 '18 at 10:10