Search code examples
scalatypesargumentsrepeatvariadic-functions

Scala: Matching vararg (repeating arguments) by type of stored elements


I recently started learning Scala and am currently messing around with tutorials. I'd like to have 2 implementations of Rational Arithmetics. I have trait IRational and 2 classes implementing it: Rational and RationalAbstraction. Most of the functionality is the same so I implement default behavior in trait but I need to get the correct constructor - either for Rational or RationalAbstraction. To this end I have a function:

def constructorImpl(numerator: Int, denominator: Int, first: IRational, irationals: IRational*): IRational = {
  println(s"first class: ${first.getClass.getSimpleName}, irationals class: ${irationals.getClass.getSimpleName}")
  first match {
    case rational: Rational => irationals match {
      case rationals: Seq[Rational] => new Rational(numerator, denominator)
      case _ => throw new UnimplementedCaseException(this, "constructorImpl", first +: irationals: _*)
    }
    case abstraction: RationalAbstraction => irationals match {
      case abstractions: Seq[RationalAbstraction] => new RationalAbstraction(numerator, denominator)
      case _ => throw new UnimplementedCaseException(this, "constructorImpl", first +: irationals: _*)
    }
  case _ => throw new UnimplementedCaseException(this, "constructorImpl", first +: irationals: _*)
  }
}

Unfortunately it doesn't work.
case rationals: Seq[Rational] => new Rational(numerator, denominator)
Does not match varargs containing Rational, allows also RationalAbstraction.
Why is that? How to match varargs by type?
Do I need to write function that unwraps irationals: _* one by one and check the head's (first elements') type?

This is the project github repository: https://github.com/axal25/LearnScalaMavenBasics

Call to the function test case [256 line]: https://github.com/axal25/LearnScalaMavenBasics/blob/master/src/main/scala/org/exercises/scala/ool/ObjectOrientedProgramming.scala

Function implementation [106 line]: https://github.com/axal25/LearnScalaMavenBasics/blob/master/src/main/scala/org/exercises/scala/ool/arith/ration/IRational.scala

If someone has some good example material (tutorial) illustrating how those damn pesky varargs work in Scala I'd be grateful.

Edit:
The purpose of the constructorImpl method is to pick the correct constructor for arithmatic operations (add/+, sub/-. mul/*, div//).
first: Irational argument is 1st argument of the operation (add, sub, mul, div).
irationals: Irational* argument is 2nd, 3rd, ... n-th argument of the operation (add, sub, mul, div).
Some operations require 2 objects of IRational implementation, some require 1 object of IRational impl. and for example Int, but always at least 1 IRational impl object. So picking the correct constructor depends on those IRational impl objects and requires that they both are of the same implementation. If 2 (or more) objects of IRational implementation are of different implementation (combination of Rational and RationalAbstraction) we don't know what constructor to call, so there should be exception thrown.

The solution in this hierarchy (without using generics):

  def constructorImpl(numerator: Int, denominator: Int, first: IRational, irationals: IRational*): IRational = {
    println(s"first class: ${first.getClass.getSimpleName}, irationals class: ${irationals.getClass.getSimpleName}")

    @scala.annotation.tailrec
    def isSeqElementsOfTypeSameAsFirst(first: Any, irationals: Any*): Boolean = irationals match {
      case Seq() => true
      case Seq(head, tail@_*) => {
        if (first.getClass == head.getClass) isSeqElementsOfTypeSameAsFirst(first, tail: _*)
        else false
      }
      case _ => false
    }

    if (isSeqElementsOfTypeSameAsFirst(first, irationals: _*)) {
      first match {
        case rational: Rational => new Rational(numerator, denominator)
        case abstraction: RationalAbstraction => new RationalAbstraction(numerator, denominator)
        case _ => throw new UnimplementedCaseException(this, "constructorImpl", first +: irationals: _*)
      }
    }
    else throw new MixingIRationalImplementationException(first, irationals: _*)
  }

question commit: https://github.com/axal25/LearnScalaMavenBasics/commit/c5a113b0361d8632bb39bbfc7ed7f7cd329a2da1
solution commit: https://github.com/axal25/LearnScalaMavenBasics/commit/1920128ba2aedac4fa9671311ec56dcc09dc7483


Solution

  • Do I need to write function that unwraps irationals: _* one by one and check the head's (first elements') type?

    Yes, you need to check each element. irationals could contain elements of any subclass of IRational and they don't all have to be the same subtype, so you need to check each one. But it is not clear what the purpose of irationals is, so the question needs more detail.