3

At first I had believed that using underscores to make closures (e.g. println _) were just shorthand for using an arrow (e.g. x => println x), but I just recently learned that you can also do the following:

def f(a: Int, b: Int) = a + 2 * b
List(1, 2, 3).reduce(f _)

Given my past assumptions, f _ looks like a closure that accepts exactly one argument and passes exactly one argument to f. I assumed it would tell me it couldn't compile because f expects two arguments, and reduce should expect a function with two arguments. But it works as if I had written:

def f(a: Int, b: Int) = a + 2 * b
List(1, 2, 3).reduce((x, y) => f(x, y))

What is going on here? What are the rules for creating closures with underscores?

Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
math4tots
  • 8,540
  • 14
  • 58
  • 95
  • 2
    In your case, `f _` is a function type using the eta-expansion mechanism. That is, it translates the method `f(a: Int, b:Int):Int` into `(Int, Int) => Int` (i.e a `Function2[Int, Int, Int]`), which is a valid type to provide to reduce. – Alexis C. Oct 18 '16 at 21:28
  • (also as a quick note, your function is not associative, which broke the contract required by reduce) – Alexis C. Oct 18 '16 at 21:34
  • @math4tots also, be aware that `f _` and `f(_)` are two different things. – fxlae Oct 18 '16 at 22:54

1 Answers1

5

Nothing special going on. Method reduce takes a function that takes two Ints and produces an Int, so providing it with an f works fine. Note that when you say f _ that actually expands to x => f x (or, in case of two parameters such as here, (x, y) => f(x, y)). You can also just provide f which will then be used directly, without the extra anonymous function wrapper.

Transforming a method into a function by doing f _ is called eta-expansion (full disclosure: I wrote that article). Difference is subtle; function is a value, while a method is, well, a method that you invoke upon an object it's defined for, e.g. myObject.myMethod. Function can stand alone, be held in collections etc. Defining your method f directly as a function would be val f: (Int, Int) => Int = (a: Int, b: Int) => a + b or, with type inference, val f = (a: Int, b: Int) => a + b.

BTW I don't see how this is a closure.

slouc
  • 9,508
  • 3
  • 16
  • 41
  • What's the difference between a partially applied function and eta expansion? E.g. `val k = f _`, isn't that also a partially applied function? – ceran Oct 18 '16 at 21:45
  • @ceran No. Partially applied function would be `f(42, _: Int)`. Or if it's curried as `def f(a: Int)(b: Int)` then you can say `f(42) _`. But just saying `f _` means that a method `f` will be transformed into a function. Also see edit in my answer for further clarification. – slouc Oct 18 '16 at 21:52
  • Hm I don't see the difference. Odersky's Scala book says that in`list.foreach(println _)`, `println _` represents a partially applied function. You don't need to supply any argument at all. – ceran Oct 18 '16 at 21:59
  • 1
    You are right, my wording is wrong. Yes, technically it's a partially applied function. Even compiler will tell you something like "add _ if you want to treat it as a partially applied function". What I meant was, `f _` will take your method, perform eta expansion (that is, turn it into a function `Int => Int` by wrapping it into `x => f x`) and then it will apply zero arguments to it, thus making it a partially applied function. If you consider a function application with zero parameters a PAF then every function is a PAF, which is a bit misleading, but yes, technically you are correct. – slouc Oct 18 '16 at 22:09
  • 1
    So, improved answer to your previous question - PAF is a function that comes out as a result of partially applying some existing function by not providing all arguments (if all arguments are applied, it's no longer "partially applied" and is in fact no longer a function, but the value that the function returned). Eta expansion is the process of taking a function and wrapping it inside another function, e.g. take `Math.sqrt(x)` and wrap it into `x => Math.sqrt(x)`. They both take some `Int` and return its square root. Eta exp is performed by the compiler when turning a method into a function. – slouc Oct 18 '16 at 22:18
  • Thank you, think I got it now. – ceran Oct 18 '16 at 22:20
  • And furthermore, `f _` *is* technically a partially applied function as you correctly pointed out. But this part is kind of irrelevant since not applying any arguments just leaves the function as is. Important part that happens with `f _` is the eta expansion, which wraps the method `f` into a function `x => f x`, which gives us a *value* to work with. A value of type function. Method is not a value (can't be contained in collections, taken as function parameter, returned from a function etc.). – slouc Oct 18 '16 at 22:21
  • Sorry about the late reply, but in your answer you say "Note that when you say `f _` that actually expands to `x => f x`". I'm not sure I understand. If I replace `f _` with `x => f x`, it doesn't compile. The link to eta-expansion was helpful though :) – math4tots Oct 19 '16 at 18:13
  • Again, my bad for not clearing it up. This stuff is quite complex and I simply overlook some parts (meaning I take things for granted and forget to explain them). So, your function takes two parameters, not one, so `x => f x ` cannot work. Proper eta expansion in this case needs to involve two parameters: `(x, y) => f(x, y)` will work. – slouc Oct 19 '16 at 19:28