Ismail Marmoush
Ismail Marmoush

Reputation: 13590

scala passing function with underscore produces a function not a value

Hi I was writing any possible variations of passing a function to map, my initial understanding that they would all produce the same result, but I found that the lines 2, 3, actually produced different output, and line 4 is a mystery to me

def g(v: Int) = List(v - 1, v, v + 1)
    val l = List(1, 2, 3, 4, 5)
    // map with some variations
    println(l.map { x => g(x) })
    println(l.map { (_: Int) => g(_) }) // line 2
    println(l.map { (_) => g(_) }) // line 3
    println(l.map { _ => }) // line 4
    println(l.map { g(_) })
    println(l.map { g })

Output:

List(List(0, 1, 2), List(1, 2, 3), List(2, 3, 4), List(3, 4, 5), List(4, 5, 6))
List(<function1>, <function1>, <function1>, <function1>, <function1>)
List(<function1>, <function1>, <function1>, <function1>, <function1>)
List((), (), (), (), ())
List(List(0, 1, 2), List(1, 2, 3), List(2, 3, 4), List(3, 4, 5), List(4, 5, 6))
List(List(0, 1, 2), List(1, 2, 3), List(2, 3, 4), List(3, 4, 5), List(4, 5, 6))

Upvotes: 3

Views: 609

Answers (2)

Daniel C. Sobral
Daniel C. Sobral

Reputation: 297305

Let's start with the ones returning the results you expected:

println(l.map { x => g(x) })
println(l.map { g(_) })
println(l.map { g })

One can infer that all three are the same thing. Actually, the third one depends on the fact that map expects a function, but, truthfully, they really are basically the same.

Starting with the last, g is a named function. You defined it with the name g, taking an Int and returning a List[Int].

Next, the first one: x => g(x). This is an anonymous function. It doesn't have a name, and it's definition is right there, where it is written: it takes a parameter x (which was inferred to be Int), and returns a List[Int] (because that's what calling g(x)) does.

Now, g and x => g(x) are not the same thing. They have the same type, Int => List[Int], but they are different functions, just like, if I defined def h(x: Int) = g(x), h and g would have the same type but would not be the same function.

That leaves g(_). That is a syntactic sugar for x => g(x). In this case, g(_) and x => g(x) are really the same thing. This is a special case of something like _ + _, an expression where the underscores represent the parameters to a function. One would think that g(_) would be equal to g(x => x), but this case, where the expansion would be x => x, is an exception that "moves" the function to the next outer expression boundary.

So, let's see the cases that gave you doubts:

println(l.map { (_: Int) => g(_) }) // line 2

First, we know that g(_) is the same thing as x => g(x), so this line would be equivalent to

println(l.map { (_: Int) => (x => g(x)) }) // line 2

The remaining underscore is completely unrelated to the one in g(_). An underscore in place of a parameter name means the parameter name is irrelevant. It's essentially the same thing as writing this:

println(l.map { (unusedVar: Int) => (x => g(x)) }) // line 2

As for the type, it is Int => (Int => List[Int]). So, when you map the list, you get a list of Int => List[Int]-- functions! -- which is what gets printed.

println(l.map { (_) => g(_) }) // line 3

Same thing as line 2, except that you omit the type of the parameter, which is going to be inferred anyway.

Finally,

println(l.map { _ => }) // line 4

The type of that is Int => Unit. It simply takes a parameter, which is ignored, doesn't do anything, and returns a Unit (which is something like the void type in Java).

Unit is a type for things whose value doesn't matter. A function has to return something, because that's what functions do. When that something doesn't matter, we use the Unit type, which has only one value. The sole value of Unit is written as ().

For example, this returns true:

def test = {
  val a = println("Println returns Unit")
  val b: Unit = () // the only possible value
  a == b
}

And that's why you see a list of () get printed for line 4: it's a List[Unit], and, therefore, all elements of it are ().

Upvotes: 3

Michael Zajac
Michael Zajac

Reputation: 55569

These are all equivalent ways of passing the application of the function g to every element of the List:

l.map { x => g(x) }
l.map { g(_) }
l.map { g }

res17: List[List[Int]] = List(List(0, 1, 2), List(1, 2, 3), List(2, 3, 4), List(3, 4, 5), List(4, 5, 6))

These are equivalent ways of mapping all of the elements of the List to an unapplied function like g:

l.map { (_: Int) => g(_) }
l.map { (_) => g(_) }

That is, each element of the mapped list is actually g.

scala> l.map { (_: Int) => g(_) }.head
res23: Int => List[Int] = <function1>
scala> res23(0)
res24: List[Int] = List(-1, 0, 1)

In fact, the only difference between those two is the parenthesis and type annotation. They are both equivalent to:

l.map { _ => g(_) }

The following just maps all the elements of the List to Unit:

l.map { _ => }

Upvotes: 6

Related Questions