Passing a binary function to List[(Int,Int)].map

Let define a list of tuple of integer.

val as = List((1, 1), (2, 4), (3, 9))

When creating a binary function variable f : (Int, Int) => Int, we cannot pass it directly to as.map like this:

val f : (Int, Int) => Int = (i, j) => i + j
as.map(f)
/*Error */
1 |as.map(f)
  |       ^
  |       Found:    (f : (Int, Int) => Int)
  |       Required: ((Int, Int)) => Any

This is because the function required for List[A].map must be a unary function accepting a type A (with signature def map[B](f: A => B): List[B]). Here A is (Int, Int).

Normally, we need to convert f like this:

as.map(f.tupled)
/* Output */
val res4: List[Int] = List(2, 6, 12)

So far everything makes sense.

However, if, instead of creating a function variable, we pass a “binary” anonymous function to map right away, it turns out that doing this produces the same result without the need to converting a function at all:

as.map((i, j) => i + j)
/* Output */
val res5: List[Int] = List(2, 6, 12)

What are mechanisms behind this behavior?

I guess that last example was Scala 3? If so, they have added more sugar syntax to improve the ergonomics at the expense of complexity.

You’re right! I use worksheet that run Scala 3.

This is parameter untupling.

When reading that doc and the “more details” it seems to me that the function value also should work, as it says:

" Generally, a function value with n > 1 parameters is converted to a pattern-matching closure using case if the expected type is a unary function type of the form ((T_1, ..., T_n)) => U."

So should not this also cover the case of a function value f as in

Welcome to Scala 3.1.1 (11.0.13, Java OpenJDK 64-Bit Server VM).
Type in expressions for evaluation. Or try :help.
                                                                                
scala> val xs = List((1,2),(3,4))
val xs: List[(Int, Int)] = List((1,2), (3,4))
                                                                                
scala> val f : (Int, Int) => Int = (i, j) => i + j
val f: (Int, Int) => Int = Lambda$1432/0x00000008007c1840@5b1420f9
                                                                                
scala> xs.map(f)
-- [E007] Type Mismatch Error: -------------------------------------------------
1 |xs.map(f)
  |       ^
  |       Found:    (f : (Int, Int) => Int)
  |       Required: ((Int, Int)) => Any

longer explanation available when compiling with `-explain`
1 error found
                                                                                
scala> xs.map{case (x,y) => f(x,y)}
val res0: List[Int] = List(3, 7)


?

There’s some discussion in the SIP. The difference is that for function literals, the function is defined at the usage site and can directly be created in the required unary/tupled shape, thus no unexpected instantiation, whereas a new function would have to be created, perhaps unexpectedly so, for a reference to an existing n-ary function.

Thanks for the pointer @sangamon . So the argument is one of performance: don’t create yet another function object in this case. Well, I think I might have prioritized regularity over performance here, but at least I now know why the behavior is irregular :). All those performance tweaks are a bit strange to a beginner programmer though…

1 Like

I added some words to https://dotty.epfl.ch/docs/reference/other-new-features/parameter-untupling.html based on that linked thread.

The words are

the adaptation is applied to the mismatching formal parameter list. In particular, the adaptation is not a conversion between function types.

I added the “user” conversion that turns out to be exactly what you’d get if the compiler supported the “extra” adaptation. It’s efficient because of “transparent inline” in Scala 3.

Actually, that page does not make it clear that “this is an efficient conversion”. I did remove the previous words on the “details” page that suggested it would not be efficient. And actually, I just tried a quick example and I see boxing where there should be none. So maybe the takeaway is, I didn’t become a programmer because I love gotchas.

1 Like

I guess the phrase “not a conversion between function types” might leave a few beginners with blank, distant-looking eyes, so perhaps add an example of that “not”-case?

BTW: You can borrow my blue and yellow T-shirt with “Make love not gotchas”, if you like :slight_smile: :tshirt: