1

I am trying to use placeholder syntax for simple map:

Array(1,2,3).map(if(_ % 2 == 0) _ * 2)

I was expecting to have the same effect as:

Array(1,2,3).map(i=>if (i%2==0) i * 2)

It complains

error: type mismatch;
 found   : Unit
 required: Int => ?

I also tried:

Array(1,2,3).map(if(_ % 2 == 0) _ * 2 else _)                    //with else
Array(1,2,3).map(if((_:Int) % 2 == 0) (_:Int) * 2 else (_:Int))  //All typed
Array(1,2,3).map(if((_:Int) % 2 == 0) 0 else 1)                  //Typed and specific return Int

Every one of them gives error. How to properly use this syntax in this case?


Edit

The link states that filter(_:Boolean) and filter (_ == true) should work, but my trials with specific typing does not work. This link also states that if (_) x else y should work, but in my case it does not. Need more explanation.


Edit 2

Tried:

Array(true,false).map(if(_) 0 else 1)

It works. But my case:

Array(1,2,3).map(if((_) % 2 == 0) 0 else 1)

Does not work.

Does this syntax only support such simple expressions?

SwiftMango
  • 15,092
  • 13
  • 71
  • 136
  • Here is a very good explanation about placeholders https://stackoverflow.com/questions/7695270/underscore-in-list-filter – eliasah Jun 14 '18 at 15:01
  • you can find the rules for placeholder [here](https://stackoverflow.com/questions/1025181/hidden-features-of-scala/1083523#1083523). – Franck Cussac Jun 14 '18 at 15:05
  • I read both links. In the first one, it says it works with `.filter(_:Boolean)` and `filter (_ == true)` , and I did try to put type, and full expression, but it still does not work. In the second link it says `if (_) x else y` works, but mine does not. Any more explanation in my cases? – SwiftMango Jun 14 '18 at 15:30
  • I've not read the spec but in my experience, this only really reliably works if the underscore is the first thing inside the `.map`. Using your example, something like this would work if you want a one-liner and don't mind declaring a name for the value: `for (x <- Array(1, 2, 3)) yield if (x % 2 == 0) 0 else 1`. `if((_: Int) % 2 == 0)` won't work as the type of `(_: Int) % 2 == 0` is `Int => Boolean`, whereas an if statement will only accept type `Boolean`. – James Whiteley Jun 14 '18 at 15:57
  • @JamesWhiteley It looks like to me that the expansion only happens within the immediate parenthesis around the underscore. If there is more expression outside, it does not consider into account? – SwiftMango Jun 14 '18 at 16:03
  • That `if (_) x else y` works doesn't mean that `if () x else y` works. – Alexey Romanov Jun 15 '18 at 07:15
  • @texasbruce "It looks like to me that the expansion only happens within the immediate parenthesis around the underscore" Basically yes; if it was otherwise, why would it stop at `map` and not become `(i, j) => Array(1,2,3).map(if (i%2==0) j * 2)`? – Alexey Romanov Jun 15 '18 at 07:26
  • @AlexeyRomanov I should have said "immediate simple expression/statement" than "immediate parenthesis" because you can put parenthesis on a simple variable... But yes, I was expecting the expansion to go beyond the whole if statement, but it only expanded inside the if condition. Apparently if the if condition is just the _ itself, it can expand. Otherwise it only expands within the condition statement – SwiftMango Jun 15 '18 at 14:09
  • @texasbruce For "immediate simple expression" you'll get a wrong result for `_ * 2 + 3`: `(x => x * 2) + 3`. "Immediate parentheses" is actually closer, though requires some caveats/ – Alexey Romanov Jun 15 '18 at 17:51
  • @texasbruce Strictly speaking, it's "immediate `Expr` (as defined by the grammar in https://www.scala-lang.org/files/archive/spec/2.12/06-expressions.html) _which isn't just the `_` or `_: Type` itself_". – Alexey Romanov Jun 15 '18 at 17:55

1 Answers1

3

Re: your original question:

Array(1, 2, 3).map(if (_ % 2 == 0) _ * 2)
// error: missing parameter type for expanded function ...
// error: type mismatch ...

The _ placeholder used in such pattern represents positional parameters of an anonymous function, hence if (_ % 2 == 0) _ * 2 is equivalent to:

if (x => x % 2 == 0) y => y * 2

That explains the missing parameter error. The type mismatch error is due to missing else in the if statement, forcing the compiler to return a Unit:

Array(1, 2, 3).map(i => if (i % 2 == 0) i * 2 else i)
// res1: Array[Int] = Array(1, 4, 3

Given that you need to specify the single input multiple times I'm not sure it'll work for you.

As you've already noticed, this placeholder syntax does have its limitation and should be treated as a convenient shortform for relatively simple cases. Like the if (_ % 2 == 0) 0 else 1) case, nesting _ within a map generally does not work well. For example:

List("a", "b", "c").map(_ * 3)
// res2: List[String] = List(aaa, bbb, ccc)

List("a", "b", "c").map((_ + " ") * 3)
// error: missing parameter type for expanded function ...

For more usage re: _, here is a SO link, and a Scala doc about commonly used symbols including _.

Leo C
  • 22,006
  • 3
  • 26
  • 39
  • @texasbruce This is not a limitation, it is just a design decision. If `_` meant "the first parameter" then there would be no shortcut for the second parameter. As it stands you can access multiple parameters using `_` as long as you reference each one once, e.g. `.reduce(_*0.9 + _)` – Tim Jun 15 '18 at 06:22
  • 2
    Your original translation is incorrect: that expression wouldn't have type `Unit` as the error message shows. I think it's actually `if (x => x % 2 == 0) y => y * 2`. – Alexey Romanov Jun 15 '18 at 07:22
  • @Tim I meant the other limitation, like I was expecting the expansion to go beyond the whole if statement, but it only expanded inside the if condition. Apparently if the if condition is just the _ itself, it can expand. Otherwise it only expands within the condition statement – SwiftMango Jun 15 '18 at 14:25
  • Good catch @Alexey Romanov. Thanks. I've updated the answer. – Leo C Jun 15 '18 at 15:25