Timothy Jones
Timothy Jones

Reputation: 22125

Function calls with returned tuple without intermediate value

I have a function in scala that returns a tuple:

private def someFunction(from: List[Any], to: List[Any]): (List[Any], List[Any]) = {
   // Do some stuff
   (modifiedFrom, modifiedTo)
}

I want to decompose that tuple, and pass the values as parameters to another function:

@tailrec
someOtherFunction(first: List[Any], second: List[Any], third: List[Any]): Unit = {
   // Some more stuff
   val (two, three) = someFunction(foo, bar)
   someOtherFunction(one, two, three)
}

Is it possible to instead write something like:

someOtherFunction(one, someFunction(foo,bar)) // This is a compile error.

Can I write the decomposition in a cleaner way?

Upvotes: 2

Views: 79

Answers (3)

dk14
dk14

Reputation: 22374

Not so pretty (scala wanted type ascription), but if you really want one-liner:

def f1(): (Int, Int) = (2,3)

def f2(a: Int, b: Int, c: Int) = 0

(f2(1, _: Int, _: Int)).tupled(f1())

Explanation:

  • tupled is a method defined for every Function instance (as first-class citizen lambda). It returns a function that can accept tuples.

  • f2(1, _: Int, _: Int) is a partial application - it returns a function from second and third argument here, so it could be "tupled" afterwards

P.S. You could avoid type-ascription ugliness, by redefining f2 as:

def f2(a: Int)(b: Int, c: Int) = 0
f2(1) _ tupled f1()

Update. If you don't want to break tail-recursion, use TailCalls:

import scala.util.control.TailCalls._
def f2(a: Int)(b: Int, c: Int): TailRec[Int] = 
  if (false) tailcall(f2(1) _ tupled f1()) else done(0)

f2(1)(2, 3).result

The additional advantage here is that if your f2 gets more complex - it's easier to trace tail-positioned calls in the code. It also supports things like mutual tail-recursion.

Explanation:

  • tailcall marks a tail-recursive call
  • done marks value you want to return in the end of loop
  • .result runs stack-safe computation and extracts result from TailCall[T]. You can also notice that TailCall wrapper plays simillar role to @tailrec - it doesn't allow non-tail-positioned call, as it would require to "unwrap" the result. Compiler-level optimization is being replaced by trampolined computation, which is also stack-safe.

Upvotes: 3

jwvh
jwvh

Reputation: 51271

If someOtherFunction were defined with two parameter groups then it'd be easy.

Instead of this ...

val (two, three) = someFunction(foo, bar)
someOtherFunction(one)(two, three)

... you could do this.

someOtherFunction(one) _ tupled someFunction(foo, bar)

But short of that you'll probably have to break the tuple into its parts.

Upvotes: 2

Aivean
Aivean

Reputation: 10882

I don't think that there is a cleaner way out of box. Perhaps it's possible to do something using macros, but it kinda defeats the purpose.

Only alternative I can think of (sort of cleaner, because it doesn't pollute the namespace) is following:

someFunction(foo, bar) match {
  case (two, three) => someOtherFunction(one, two, three)
}

Upvotes: 2

Related Questions