It doesn't. It's invalid code in most (all?) cases.
scala> val f: Int => Int = _ - 1
f: Int => Int = <function1>
scala> val ary = Array(1,2,3)
ary: Array[Int] = Array(1, 2, 3)
scala> ary.map(f(_ * 2))
<console>:10: error: missing parameter type for expanded function ((x$1) => x$1.$times(2))
ary.map(f(_ * 2))
^
You can't think of "_" like a placeholder. It's not. It's to lift an argument of a function.
So simplify it: map in this context wants a Function[Int, Int] right? So f(_ * 2) must return a Function[Int,Int]. But it doesn't probably. _ is lifting some argument out of whatever f is. If you assume _ is an Int, does f take a Function[Int,Int]? No. It takes an Int. So there's no way to parse this that makes sense. It's not just "I have a stack of vars, pull one off the stack and bind it every time I write an underscore, reading left to right". That would be some AST generative grammar hack. That's not what the underscore is. It's simpler and more consistent than that.
What you're probably looking for instead is Function Composition. So something like:
So why does that work? Because we were able to compose f() into a larger function that satisfies the signature of the argument map[T](Int => T) requires. A lot like a Stream conceptually.
(Is it correct to say in this context f() is a Monad? I'm not sure, I need to sit down and grok the category stuff sometime...)
Or you can write it the long way (calling f inside a new function). But instead of defining "steps" you'd be creating a new imperative function and driving the stack.
That's interesting, but you did not answer my question. I wanted to know how Scala determines where to insert =>. In my example, Scala interprets "ary.map(f(_ * 2))" as "ary.map(f(x => x * 2))" (both of these expressions yield the same error). If it interpreted it the other way, then the expression would work (using your definition of f). Perhaps a better example to use would have been:
As it turns out, Scala goes for the latter. After playing with the feature a bit I'd say the rules are engineered to fit particular use cases, but they can be a bit finicky if you venture outside of them. For instance, "1 + _ * 2" will work, but "(1 + _) * 2" won't. "f(_)(x)" does not mean the same thing as "(f(_))(x)".
I mean, I wouldn't say the feature is bad, but it definitely has a DWIM vibe to it that would make me classify it as a hack.
> In my example, Scala interprets "ary.map(f(_ * 2))" as "ary.map(f(x => x * 2))"
I think this is where I'm not communicating what I'm trying to say very well. What I'm trying to say is that that statement is false.
The underscore isn't a placeholder saying "inject a Function here". You probably didn't mean that exactly of course, but it's a programming language; it pays to be a bit pedantic I think.
It's easier to understand if you work backwards maybe.
Is this valid?
(_:Int) * 2
Of course. It's a Function[Int,Int]. The important part is the asterisk method that's being lifted to a Function.
So now you take that perfectly valid piece of code lifting a method to a function and you pass it to Array.apply:
Array.apply((_:Int) * 2)
Why should your code start behaving differently? That wouldn't be consistent at all. You had a Function[Int,Int] before, but now that you've passed that function value to Array.apply instead of getting an Array[Function[Int,Int]] as you would in every single other case, you get a Function[Int,Array[Int]]?
What sense does that make? That seems like straight up voodoo.
There's some syntax supporting this feature. But not so much as you think. It's just the "lift". Type-inferencing lets you get away with what looks like a little more sometimes.
> The underscore isn't a placeholder saying "inject a Function here".
It is. Look at the error message:
error: missing parameter type for expanded function ((x$1) => x$1.$times(2))
It did inject a function, it's right there, in plain text. Then it did type inference. I mean, what else is "lifting" the asterisk to a Function supposed to mean, if not injecting a function around a placeholder? And what about "1 + _ * 2"? What is it lifting? The asterisk? No. It is lifting more than that.
> You had a Function[Int,Int] before, but now that you've passed that function value to Array.apply instead of getting an Array[Function[Int,Int]] as you would in every single other case, you get a Function[Int,Array[Int]]?
That's besides the point. The question is, when the parser sees "_", how much of the context does it grab along with it? In other words, I know it's lifting stuff, what I want to know is how much it lifts. Here's another example:
Scala does the former. That's a legitimate choice given common use cases, but the latter is simpler, preserves the invariant that "a(b) <=> (a)(b)" and has use cases as well, e.g. to make an expression like "f(super_long_expression, 2)" more readable.
> But not so much as you think. It's just the "lift". Type-inferencing lets you get away with what looks like a little more sometimes.
I still don't see what type inference has to do with this. The error message makes it clear that the lift is done before type inference kicks in. In a dynamic language, you would stop at the lift, but it would otherwise work just the same.
> It did inject a function, it's right there, in plain text. Then it did type inference.
Yes, the inferencer/type-resolver runs after the parser (there's a recent thread on the ML about a Parboiled parser you might find interesting BTW). My point was this is parsed. It's an AST at that point, it just hasn't resolved the types yet. It's not a source preprocessor. To me that implies limitations, but also an expectation of consistency.
> ... what about "1 + _ * 2"? What is it lifting? The asterisk? No. It is lifting more than that.
Yes! Paste your code into the REPL and see what you get:
scala> 1 + _ * 2
<console>:8: error: missing parameter type for expanded function ((x$1) => 1.$plus(x$1.$times(2)))
1 + _ * 2
This is a real hairy example IMO since it's conflating the mechanics with optional parenthesis and dot-less method calls, but we can break it down just the same. Since Int.+ takes a single argument, then it must parse as:
1.+(_ * 2)
NOT
(1 + _) * 2
The first example is trying to lift $times from something. The second is trying to lift the instance-method that's already bound to 1:Int to a Function0[Int].
They're doing exactly the opposite thing right? It's like assuming that the LHS and RHS of an assignment are interchangeable. They're not. One is a label and one is a value.
> how much of the context does it grab along with it?
None. In this position, it's the beginning of an expression. It doesn't escape the scope it's defined in. Just like in the 1.+(_ * 2) example, what comes before it doesn't matter. It's the beginning of an expression.
> I still don't see what type inference has to do with this.
I probably just complicated things bringing it up. ary.map(_ * 2) vs ary.map((_:Int) * 2). I feel like maybe it's not understood that those do the same thing. The only difference is the type is inferred from the signature of map[T] (simplified minus CBF) on the first.
I'm not trying to debate that sometimes it's frustrating that I can't just:
Intuitively, it seems like it's possible it could work. And it might if the underscore was accomplished through code-rewriting of some sort right? But it's not. So what I'm doing is:
I'm not satisfying the signature of foreach because tell() isn't returning a Function[JsObject,Nothing], and I'm passing a Function[JsObject,JsObject] to Queue.apply (case class Queue(payload:JsObject)). Neither of those things makes sense. So what I do instead:
(couchdbActor ! _) // This just lifts the tell method, which gives me a Function[Any,Unit].
Queue.apply // Function[JsObject,Queue]: I can compose that with my lifted tell to get a Function[JsObject,Unit]
json.merge // Function[JsObject,JsObject]: Doesn't change the signature: Function[JsObject,Unit]
Now foreach() gets passed a Function[JsObject,Unit]. Which is exactly what it's expecting.
When you break it down, you have to keep a "stack" in your head. Which can get hard sometimes (at least for me), but the underlying consistency about what's actually happening makes it simple to do. It's like a basic addition problem:
27 + 34 + 192 + 11
I expect for most people that takes some deliberate thought, breaking it into smaller operations, carrying the result in your head as you go. But it's still simple, because it's consistent.
> In a dynamic language, you would stop at the lift, but it would otherwise work just the same.
I was going to attempt to write a Ruby analogue, but I can't actually figure out a clean way to do that.
I dunno. I feel like you probably understand all this just fine. You just don't like it. You'd prefer a sort of source-rewriting approach? So maybe Scala just isn't the language for you. I feel like once you understand the mechanics of the underscore, it's pretty straight-forward. Similar to beginners getting tripped up on for-comprehensions:
val o = Option(1)
val f = Future.successful(List(2,3,4))
for {
n <- o
l <- f
x <- l
} yield n + x
That trips up everybody at some point right? If you understand that it's just map, flatten and filter, it's clear why that doesn't work though. How do you say: Option(Future(2)).flatten? What would that do? Can't work. It may be initially frustrating for new users, but it's consistent. If you study the mechanics, then it becomes second nature. The mystery disappears. That's really all I was trying to do here. Hopefully someone finds it useful.
Side note: This is a big part of why I find for-comprehensions mostly useless. They do nothing you can't already do, and they introduce what can look like magic. They're pure sugar, except more often than not, they're also longer to write. Plus they're mostly useless outside of testing. How often do you want a Failure to just throw? Or a Play Action to return a None? More often than not, you're gonna want to fold(), getOrElse(), match { case None => NotFound; case Some(foo) => ... Ok(finalResult) } etc. But I digress. :-)
> This is a real hairy example IMO since it's conflating the mechanics with optional parenthesis and dot-less method calls, but we can break it down just the same. Since Int.+ takes a single argument, then it must parse as:
> 1.+(_ * 2)
No, my point is that this is not how it parses. Look:
In the first case, the parser lifts _ over $times AND over $plus. The function is being lifted over both of them. The optional parentheses break the feature if you insert them: "1.+(_ * 2)" tries to add one to a function, which is not a valid operation.
>> how much of the context does it grab along with it?
> None. In this position, it's the beginning of an expression. It doesn't escape the scope it's defined in. Just like in the 1.+(_ * 2) example, what comes before it doesn't matter. It's the beginning of an expression.
If that was true, then "1 + _ * 2" would be invalid, and "f(_)(1)" would be equivalent to "f(1)". But the former is valid and the latter doesn't work like that.
> I was going to attempt to write a Ruby analogue, but I can't actually figure out a clean way to do that.
That only works insofar that the _ placeholder object can seize control over the expression, so "f(_, 2)" won't work unless f explicitly handles _. Implementing the full feature would require language or macro support, but my point was more that a new dynamic language could trivially support it.
So simplify it: map in this context wants a Function[Int, Int] right? So f(_ * 2) must return a Function[Int,Int]. But it doesn't probably. _ is lifting some argument out of whatever f is. If you assume _ is an Int, does f take a Function[Int,Int]? No. It takes an Int. So there's no way to parse this that makes sense. It's not just "I have a stack of vars, pull one off the stack and bind it every time I write an underscore, reading left to right". That would be some AST generative grammar hack. That's not what the underscore is. It's simpler and more consistent than that.
What you're probably looking for instead is Function Composition. So something like:
So why does that work? Because we were able to compose f() into a larger function that satisfies the signature of the argument map[T](Int => T) requires. A lot like a Stream conceptually.(Is it correct to say in this context f() is a Monad? I'm not sure, I need to sit down and grok the category stuff sometime...)
Or you can write it the long way (calling f inside a new function). But instead of defining "steps" you'd be creating a new imperative function and driving the stack.