In Scala, what is the difference between using the `_` and using a named identifier?
Asked Answered
H

3

12

Why do i get an error when I try using _ instead of using a named identifier?

scala> res0
res25: List[Int] = List(1, 2, 3, 4, 5)

scala> res0.map(_=>"item "+_.toString)
<console>:6: error: missing parameter type for expanded function ((x$2) => "item
 ".$plus(x$2.toString))
       res0.map(_=>"item "+_.toString)
                           ^

scala> res0.map(i=>"item "+i.toString)
res29: List[java.lang.String] = List(item 1, item 2, item 3, item 4, item 5)
Homager answered 1/3, 2010 at 0:4 Comment(0)
K
20

Underscores used in place of variable names like that are special; the Nth underscore means the Nth argument to an anonymous function. So the following are equivalent:

List(1, 2, 3).map(x => x + 1)

List(1, 2, 3).map(_ + 1)

But, if you do this:

List(1, 2, 3).map(_ => _ + 1) 

Then you are mapping the list with a function that ignores its single argument and returns the function defined by _ + 1. (This specific example won't compile because the compiler can't infer what type the second underscore has.) An equivalent example with named parameters would look like:

List(1, 2, 3).map(x => { y => y + 1 })

In short, using underscores in a function's argument list means "I am ignoring these arguments in the body of this function." Using them in the body means "Compiler, please generate an argument list for me." The two usages don't mix very well.

Kneeland answered 1/3, 2010 at 0:54 Comment(1)
@Homager Just to reinforce this, the underscore is used for many different purposes in Scala. As David explained, each use in your example has actually a different meaning. There are other meanings as well -- underscore, in Scala, being a pretty good example of problems arising from operator overload. While I had issues with it at first, I can honestly say I haven't ever thought of some way to improve on it.Maryellen
M
5

To complement the other answers, here are some examples showing why you get the "missing parameter type" in some cases when using '_' as a placeholder parameter.

Scala's type inference considers the 'expected' type of an expression based on its context. If there is no context, it cannot infer the type of the parameters. Notice in the error message the first and second instances of _ are replaced with the compiler generated identifiers x$1 and x$2.

scala> _ + _
<console>:5: error: missing parameter type for expanded function ((x$1, x$2) => x$1.$plus(x$2))
       _ + _
       ^
<console>:5: error: missing parameter type for expanded function ((x$1: <error>, x$2) => x$1.$plus(x$2))
       _ + _
           ^

Adding a type ascription to the entire expression provides enough context to help the inferencer:

scala> (_ + _) : ((Int, Int) => Int)
res3: (Int, Int) => Int = <function2>

Alternatively, you can add a type ascription to each parameter placeholder:

scala> (_: Int) + (_: Int)          
res4: (Int, Int) => Int = <function2>

In the function call below with type arguments provided, the context is unambigous and the function type is inferred.

scala> def bar[A, R](a1: A, a2: A, f: (A, A) => R) = f(a1, a2)  
bar: [A,R](a1: A,a2: A,f: (A, A) => R)R

scala> bar[Int, Int](1, 1, _ + _)
res5: Int = 2

However, if we ask the compiler to infer the type parameters, if fails:

scala> bar(1, 1, _ + _)          
<console>:7: error: missing parameter type for expanded function ((x$1, x$2) => x$1.$plus(x$2))
       bar(1, 1, _ + _)
                 ^
<console>:7: error: missing parameter type for expanded function ((x$1: <error>, x$2) => x$1.$plus(x$2))
       bar(1, 1, _ + _)
                     ^

We can help it, though, by currying the parameter lists. Here, the arguments to the first parameter list (1, 1), tell the inference that the type parameter A should be Int. It then knows that the type of the argument f must be (Int, Int) => ?), and the return type R is inferred as Int, the result of integer addition. You will see the same approach used in Traversable.flatMap in the standard library.

scala> def foo[A, R](a1: A, a2: A)(f: (A, A) => R) = f(a1, a2) 
foo: [A,R](a1: A,a2: A)(f: (A, A) => R)R

scala> foo[Int, Int](1, 1) { _ + _ }
res1: Int = 2

scala> foo(1, 1) { _ + _ }
res0: Int = 2
Moyer answered 1/3, 2010 at 6:39 Comment(0)
S
4

If you're not going to bind an identifier, just leave that part out.

res0.map("item "+_.toString)
Squeeze answered 1/3, 2010 at 0:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.