Explode a tuple for function parameters?

I have a data “generating” function with the following signature:

def myGenFunc(...): (MyData, Int) = ...

Also I have a “receiving” function like this

def myReceiveFunc(data: MyData, n: Int): SomeThing = ...

I now want to compose these two functions like this:

myReceiveFunc(myGenFunc(...).whatToDoHere())

but I don’t know what whatToDoHere() should be, i.e. how to do the exploding of the tuple in two individual parameters.

Can anyone help?

object Example{
  def producer: (Int, String) = ???
  def consumer(x: Int, v: String) = ???

  def scala3 = consumer.tupled(producer)
  def scala2 = (consumer _).tupled(producer)
}
3 Likes

@WojciechMazur How cool is this! It works! Thanks a lot.

But to understand it: Is tupled a method of all functions I define? Or probably only of functions with more than one argument, right?

And does this means this method gets added automatically to each function on function definition?

1 Like

Just like any instance of any class gets added the methods of that class?

tupled is just a normal method on (A, B) => C that transforms it into a ((A, B)) => C

Interesting! So tupled operates on the function itself, not on the function result. Then I would expect that I have to write something like

tupled(consumer, producer)

(to stay with @WojciechMazur’s example)…

Sorry for my ignorance, but I really don’t understand this use of the dot-notation.

Ah okay, I think I understand now what is the confusion.

In Scala, functions are just values, like any other value (for example numbers). Also, in Scala, all values are objects, instances of a class, and members of some types.
Thus, you can call methods on a function itself.

In particular, for this case, we need to look into the type definition of a function of a single argument and a ]function of two arguments:

// The compiler allows us to write this as I1 => O
trait Function1[I1, O] {
  // Invoking a function is just calling its apply method.
  def apply(i1: I1): O
}

// The compiler allows us to write this as (I1, I2) => O
trait Function2[I1, I2, O] {
  // Invoking a function is just calling its apply method.
  def apply(i1: I1, i2: I2): O

  // Transforms this function of two arguments,
  // into one that takes a single tuple argument.
  // It delegates to the current logic by unpacking the tuple.
  final def tupled: ((I1, I2)) => O =
    tuple => this.apply(i1 = tuple._1, i2 = tuple._2)
}

BTW, technically speaking, when you use def you are creating a METHOD, not a FUNCTION; they are different: Scala FAQ | Scala Documentation (but, in practice, you don’t need to be this pedantic).
However, it is trivial to create a FUNCTION from a METHOD, by delegation:

def aMethod(x: Int): Int = ...
val aFunction: Int => Int = x => aMethod(x)

Since this process is trivial, the compiler can do it automatically for us.
In Scala 2 it sometimes needed some help, like (consumer _) in @WojciechMazur original example.

3 Likes

Wow, this sounds very powerful!. Have to digest it…

I do get your point though, that we call tupled on the function: After all we write

consumer.tupled

not

consumer(...).tupled

Thanks for your patience!

A nice reminder of how cool Scala is!

I’m adding some migration support to Scala 2 for use of “case class companion” as a function object, to match the support in Scala 3.

Just tweaking the warning message as shown.

t3664.scala:17: warning: The method `apply` is inserted. The auto insertion will be deprecated, please write `((i: Int, j: Int) => C.apply(i, j)).curried` explicitly.
   def g(xs: List[Int]): List[C] = xs.map(C.curried).map(_(42))

to

t3664.scala:17: warning: The method `apply` is inserted. The auto insertion will be deprecated, please write `(C.apply _).curried` explicitly.

You can never have too much :sparkles: .

2 Likes