> 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:

documents foreach(couchdbActor ! Queue(json.merge(_)))

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:

documents foreach(couchdbActor.tell(Queue(JsObject => JsObject))

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:

documents foreach((couchdbActor ! _) compose Queue compose json.merge)

Breaking that down, I've got:

(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. :-)