There are many scenarios when _ placeholder syntax inference breaks under the slightest changes where you would expect it to work fine. It is very fragile syntax. Someone should compile a list, but for now here is a clear example.
First is some evidence that scala is seemingly capable of inferring the type
case class Foo(str: String)
val f: String => Unit = ???
val g: Foo => Unit = f(_)
// ^ Error
// Found: (_$1 :Foo) <-- Scala can see it's a Foo, great!
// Required: String
Yet when I attempt accessing the .str that every Foo has, the compiler suddenly loses knowledge of that fact.
val g: Foo => Unit = f(_.str)
// Missing parameter type
// I could not infer the type of the parameter _$1
// in expanded function:
// _$1 => _$1.str
Here is another one. Inference works for this code
val g: Foo => Unit =
_.str.length > 3
but fails when enclosed within an ifthen
val g: Foo => Unit =
if _.str.length > 3 then ??? else ???
// ^
// Missing parameter type
I enjoy _ and think it is a good language feature - for the same reasons it was nice in Kotlin. But it is disappointing how often it breaks.
First can someone explain these two scenarios?
Second Is it possible to make it’s behavior more reliable so I don’t feel discouraged from using it?
I see. I wonder how useful this behavior is? Are there lots of scenarios where you need this to be the behavior? It seems like it breaks many more scenarios than how ever many it makes possible.
They would need to change how it works, which may break a lot of code.
Well, to avoid breaking existing code, they could introduce a new keyword it which does not expand in it’s enclosing scope. and some bonus changes like being able to refer to same parameter multiple times rather than the much less common once-per-parameter (e.g. reduce(_ + _)) design, I see that complained about too.
This I think would be a better longterm plan over everyone collectively agreeing to never use a nice language feature for the rest of Scala’s life just because it was implemented with a few unfortunate design choices. Scala is better than Kotlin in nearly every way but I do miss this.
While I actually agree kotlin’sit is way better than _. I doubt they will ever make that change.
Having said that, the ability to do _ + _ while not that common, would be even more annoying not to have, so that is something to consider.
Anyways, if you want to promote the idea, the right place would be: https://contributors.scala-lang.org/
Personally, I do think that people over abuse the feature and that naming parameters is usually better.
The nice thing about placeholder syntax is that it is the opposite of fragile or brittle, because it’s just syntax.
I agree with the advice to rely on idioms such as reduce(_ + _) and do not over-anonymize.
Scala 2 REPL has a facility for quickly printing syntax desugaring. (That is // print followed by <tab> of autocompletion.)
scala> "hello, world".indexOf(_ > 5) // print
"hello, world".indexOf(((x$1) => x$1.>(5)))
scala> println(_.length): (String => Unit) // print
((scala.Predef.println(((x$1) => x$1.length))): scala.Function1[scala.Predef.String, scala.Unit]) // : String => Unit
scala> for (x <- List(42) if x > 10) println(x) // print
scala.`package`.List.apply[Int](42).withFilter(((x: Int) => x.>(10))).foreach[Unit](((x: Int) => scala.Predef.println(x))) // : Unit
also useful to witness for desugaring.
Scala 3 got an improved error message in 3.4:
scala> "hello, world".indexOf(_ > 5)
-- [E081] Type Error: ----------------------------------------------------------
1 |"hello, world".indexOf(_ > 5)
| ^
| Missing parameter type
|
| I could not infer the type of the parameter _$1
| in expanded function:
| _$1 => _$1 > 5
| Expected type for the whole anonymous function:
| Char
1 error found
where it tells you at the bottom what type the expansion is “competing” with.
In Scala 2:
scala> def f(i: Int) = i+1
def f(i: Int): Int
scala> f(_ + 1)
^
error: missing parameter type for expanded function ((<x$1: error>) => x$1.$plus(1))
^
error: type mismatch;
found : ? => ?
required: Int
scala> "hello, world".indexOf(_ > 5)
^
error: missing parameter type for expanded function ((<x$1: error>) => x$1.$greater(5))
^
error: overloaded method indexOf with alternatives:
(x$1: String,x$2: Int,x$3: Int)Int <and>
(x$1: String,x$2: Int)Int <and>
(x$1: String)Int <and>
(x$1: Int,x$2: Int,x$3: Int)Int <and>
(x$1: Int,x$2: Int)Int <and>
(x$1: Int)Int
does not match arguments (? => ?)
scala> "hello, world" + (_ > 5)
^
error: missing parameter type for expanded function ((<x$1: error>) => x$1.$greater(5))
The last example shows the futility of Any; your AI assistant might do better.
the ability to do _ + _ while not that common, would be even more annoying not to have, so that is something to consider.
The second sentence confuses me given the first. If _ + _ is not that common, why would it be more annoying than _ breaking all the time in common scenarios? Needing to do reduce((acc, n) => acc + n) isn’t that bad.
We either trade-off needing to explicitly name lambda parameters in X scenarios, or Y scenarios, so it makes sense to do that trade-off by what is more common. I only call reduce every few days/weeks, yet I write dozens of small single-parameter functions every day where _ would look nice.
Not sure how the latter follows from the former. Syntax can be fragile. Understanding that the notation is simple syntax sugar for this expansion does explain why it fails in so many scenarios, but the fact remains that it is unfortunately failing in those scenarios. Scenarios which are reasonable for programmers to assume would not fail, and to desire to not fail as an improvement in readability.
That the notation is this limited makes me want to avoid even reduce(_ + _) for the virtue of consistency.
I think those are good arguments; save 'em for the Contributors! Although you might have to come up with an actual proposal and write a proper pre-SIP. Maybe someone already suggested it in an earlier discussion? Try searching that forum. Take a look at other pre-SIPs to get an idea; also helps to include alternatives (like it maybe?).
The former follows from the latter because it is one simple syntactic rule. (That’s the two conditions about the enclosing Expr.)
People complain about type inference or implicit search as fragile or something that can’t be reasoned about, perhaps because of inherent complexity, lack of clarity, and just bugs.
But placeholder syntax itself just works.
One may dislike it as one dislikes optional braces, or wish for let and it, as people do.
I’m sure there have been multiple discussions and proposals, but the closest I could find was “Meanings of Underscore” which was largely about import syntax, but where Ichoran does mention placeholder syntax as more deserving of attention.
Of interest is how Odersky talks about a consistency in use of underscore as he deletes usages he deems non-conforming.