Home > Software engineering >  Is it possible to control the number of argument in a function when using varargs in compile time(Sc
Is it possible to control the number of argument in a function when using varargs in compile time(Sc

Time:07-06

Let us suppose, we have to create the OddList[ T] which contains only odd number of elements. Now can we do something like this

OddList(1,2) //Works fine
OddList(1,2,3) //Compilation error

if there is no condition of odd/even then we would simply do as below

object OddList{
 def apply[T](eles: T*) = ...
}

But can we control number of arguments that could be passed?

CodePudding user response:

One question first off: Are you including Nil as an element?

Because (1 :: 2 :: Nil).size is 2/even, your other example is 3/odd

At compile time, shapeless has Sized (see this answer: Scala, enforce length of Array/Collection parameter), but that seems to be for static, concrete values.

At runtime you can throw an IllegalArgumentException when the constructor is called

    case class OddList[T]( elements: T* ) {
        private val numElements: Int = elements.size
        private val hasOddNumElements: Boolean = numElements % 2 != 0
        require(hasOddNumElements, s"There are an even [$numElements] number of elements!")
    }

    OddList( 1, 2 ) // Runtime exception
    OddList( 1, 2, 3 ) // Works fine

CodePudding user response:

I'm not aware of any way of limiting this using some kind of built-in mechanism. A macro can be a possible solution. If you want to keep it simple, the only thing I can think of is that of asking for pairs:

object OddList {
 def apply[T](elems: (T, T)*) = ???
}

I also think that there might be some confusion with regards to naming, since odd numbers are 2n 1 and even ones are 2n (because you can split them evenly by two -- see Wikipedia). As such, you can define the following:

object EvenList {
 def apply[T](elems: (T, T)*) = ???
}

object OddList {
 def apply[T](elem: T, elems: (T, T)*) = ???
}

Probably not the most elegant solution, but definitely simple and easy to implement.

If this approach works for you, you can create quite flexible smart constructors as follows:

object Even {
  def apply[CC[_], A](
      elems: (A, A)*
  )(implicit factory: collection.Factory[A, CC[A]]): CC[A] = {
    val builder = factory.newBuilder
    for ((first, second) <- elems) {
      builder  = first
      builder  = second
    }
    builder.result()
  }
}

object Odd {
  def apply[CC[_], A](elem: A, elems: (A, A)*)(implicit
      factory: collection.Factory[A, CC[A]]
  ): CC[A] = {
    val builder = factory.newBuilder
    builder  = elem
    for ((first, second) <- elems) {
      builder  = first
      builder  = second
    }
    builder.result()
  }
}

assert(Even[Set, Int]((1, 2), (3, 4)) == Set(1, 2, 3, 4))
assert(Odd[List, String]("1", ("2", "3")) == List("1", "2", "3"))

You can play around with this code here on Scastie.

As a further improvement, I believe you can also create types to wrap the two possible returned items so that you can reason about collection size parity at compile time (if that is a requirement), but this is not covered by this example.

  • Related