In Scala Tuples and function arguments look similar but they can’t be used interchangeably.

A Tuple of two Ints can be defined as:

scala> val t2 = (2, 4)
t2: (Int, Int) = (2,4)

Given a simple sum method that takes in two Ints:

def sum(n1: Int, n2: Int) = n1 + n2

you might think that you could simply pass in your tuple t2 to invoke the sum method:

scala> sum(t2)
<console>:15: error: not enough arguments for method sum: (n1: Int, n2: Int)Int.
Unspecified value parameter n2.
       sum(t2)

Unfortunately this does not work as you can’t simply replace an argument list with a similarly-sized tuple. t2 is taken as the first argument n1, hence the error indicating that n2 has not been supplied.

One way to get this to work is to do the following:

scala> sum _ tupled t2
res0: Int = 6

Let’s break that incantation down step-by-step to make it more digestible.

  1. Convert the sum method into a Function:
scala> val f1 = sum _
f1: (Int, Int) => Int = $$Lambda$1447/998900406@31452c9
  1. Convert the function into its tupled variant:
scala> val f2 = f1.tupled
f2: ((Int, Int)) => Int = scala.Function2$$Lambda$227/234698513@3f891cfe

Tupling the sum function is merely going from (Int, Int) => Int to ((Int, Int)) => Int. Notice the extra parenthesis around the arguments.

  1. Apply the tupled function to the tupled input t2:
scala> f2(t2)
res21: Int = 6

Looking back that does look very difficult but it’s not very intuitive.

Using Underscores with Currying

I had a similar problem recently where I had a contains method defined as:

def contains[A](values: List[A], value: A, pred: A => A  => Boolean): Boolean = {
    values.exists(pred(value))
}

And a List l1 defined as:

val l1 = List(1, 2, 3)

I tried to call the contains method using underscores for the values of the pred parameter and got the following error:

scala> contains[Int](l1, 3, _ == _)
<console>:17: error: missing parameter type for expanded function ((x$1: <error>, x$2) => x$1.$eq$eq(x$2))
       contains[Int](l1, 3, _ == _)
                            ^
<console>:17: error: missing parameter type for expanded function ((x$1: <error>, x$2: <error>) => x$1.$eq$eq(x$2))
       contains[Int](l1, 3, _ == _)

You can use underscores to represent positional arguments in an argument list where you don’t need to bind it to a name. So why did this fail?

I can get the contains method to work with:

scala> contains[Int](l1, 3, x => y => x == y)
res24: Boolean = true

Conversely, why did this work?

Another interesting variant is if I change the definition of contains to contains2 that takes in an uncurried pred function:

def contains2[A](values: List[A], value: A, pred: (A, A)  => Boolean): Boolean = {
    values.exists(pred(value, _))
}

I can invoke it with the underscore syntax:

scala> contains2[Int](l1, 3,  _ == _)
res59: Boolean = true

One of the main reasons for using a curried version of pred was that I could partially apply it with the exists method on List without having to use underscores to convert the method to a function. I can still achieve the same result by currying pred where it is applied:

def contains3[A](values: List[A], value: A, pred: (A, A)  => Boolean): Boolean = {
    values.exists(pred.curried(value))
}

The reason I couldn’t use underscores to represent the parameters of the contains method is that the curried function pred, represents two argument lists; One that takes an A and returns another function that takes another A and returns a Boolean:

(A) => (A => Boolean)

Underscores can only used to represent positional arguments of a single argument list, since we have two in the curried variation of pred in contains we can’t use it.

Changing the shape of the Input Function

If I define a uncurried function isEqual as:

def isEqual[A](a1: A, a2: A): Boolean  = a1 == a2

I can call contains2 as:

scala> contains2[Int](l1, 3, isEqual)
res32: Boolean = true

If I define an isEqual2 as:

def isEqual2[A]: A => A => Boolean = a1 => a2 => a1 == a2

I can call contains as:

scala> contains[Int](l1, 3, isEqual2)
res33: Boolean = true

But if I try to call contains2 with isEqual2 we get:

scala> contains2[Int](l1, 3, isEqual2[Int])
<console>:18: error: type mismatch;
 found   : Int => (Int => Boolean)
 required: (Int, Int) => Boolean
       contains2[Int](l1, 3, isEqual2[Int])

And we can fix that by uncurrying isEqual2:

scala> contains2(l1, 3, Function.uncurried(isEqual2))
res65: Boolean = true

If we define isEqual3 with a Tuple2 as:

def isEqual3[A]: Tuple2[A, A] => Boolean = t => t._1 == t._2

And we try to invoke contains2 with isEqual3 we get:

scala> contains2(l1, 3, isEqual3[Int])
<console>:15: error: type mismatch;
 found   : ((Int, Int)) => Boolean
 required: (?, ?) => Boolean
       contains2(l1, 3, isEqual3[Int])

And we can easily fix that by untupling the parameters to isEqual3:

scala> contains2(l1, 3, Function.untupled(isEqual3))
res69: Boolean = true

Case Class Constructors

And one last example invoking a constructor of a case class:

scala> case class Person(name: String, age: Int)
defined class Person

scala> val nameAge = ("Katz", 20)
nameAge: (String, Int) = (Katz,20)

scala> val pc = Person.apply _
pc: (String, Int) => Person = $$Lambda$1565/1849401610@5417f849

If I try to invoke pc with nameAge I get an error as expected:

scala> pc nameAge
<console>:13: error: value nameAge is not a member of (String, Int) => Person
       pc nameAge

And we can solve that by tupling the constructor:

scala> pc tupled nameAge
res21: Person = Person(Katz,20)

Or more succinctly:

scala> Person.tupled(nameAge)
res22: Person = Person(Katz,20)

Hopefully this has given you some insight into the various ways to invoke functions that takes tuples, curried arguments or uncurried variants.

Some references: