Simple way with lots of cut and paste, overload the method for each tuple arity:
def range(r: (Int, Int), s: (Int, Int)) =
for { p1 <- r._1 to s._1
p2 <- r._2 to s._2 } yield (p1, p2)
def range(r: (Int, Int, Int), s: (Int, Int, Int)) =
for { p1 <- r._1 to s._1
p2 <- r._2 to s._2
p3 <- r._3 to s._3 } yield (p1, p2, p3)
def range(r: (Int, Int, Int, Int), s: (Int, Int, Int, Int)) =
for // etc up to 22
Alternatively:
def range(p1: Product, p2: Product) = {
def toList(t: Product): List[Int] =
t.productIterator.toList.map(_.asInstanceOf[Int])
def toProduct(lst: List[Int]) = lst.size match {
case 1 => Tuple1(lst(0))
case 2 => Tuple2(lst(0), lst(1))
case 3 => Tuple3(lst(0), lst(1), lst(2))
//etc up to 22
}
def go(xs: List[Int], ys: List[Int]): List[List[Int]] = {
if(xs.size == 1 || ys.size == 1) (xs.head to ys.head).toList.map(List(_))
else (xs.head to ys.head).toList.flatMap(i => go(xs.tail, ys.tail).map(i :: _))
}
go(toList(p1), toList(p2)) map toProduct
}
seems to work:
scala> range((1,2,4), (2,5,6))
res66: List[Product with Serializable] = List((1,2,4), (1,2,5), (1,2,6),
(1,3,4), (1,3,5), (1,3,6), (1,4,4), (1,4,5), (1,4,6), (1,5,4), (1,5,5),
(1,5,6), (2,2,4), (2,2,5), (2,2,6), (2,3,4), (2,3,5), (2,3,6), (2,4,4),
(2,4,5), (2,4,6), (2,5,4), (2,5,5), (2,5,6))
Your basic problem is that since Scala is statically typed, the method needs to have a return type, so you can never have a single method that returns both a Seq[(Int, Int)] and a Seq[(Int, Int, Int)] and all the other arities of tuple. The best you can do is to use the closest type that covers all of the outputs, in this case Product with Serializable. You can of course do a cast on the result e.g. res0.map(_.asInstanceOf[(Int, Int, Int)]).
Overloading the method as in the first example allows you a different return type for each arity, so you don't need to do any casting.