A simple way with a lot of cut and paste, overload the method for each attribute of the tuple:
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
As an alternative:
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 main problem is that since Scala is statically typed, the method must have a return type, so you can never have a single method that returns both Seq[(Int, Int)] and Seq[(Int, Int, Int)] and all other tuple objects. The best you can do is use the closest type that spans all outputs, in this case Product with Serializable . You can, of course, make a throw at the result, for example. res0.map(_.asInstanceOf[(Int, Int, Int)]) .
Method overloading, as in the first example, allows you to use a different type of return value for each arity, so you do not need to do casting.