Hey Scott, thanks for taking the time to write a detailed response.
First to answer your question, foo(bar(42, _ + 1))
would be foo(bar(42, |x| x + 1))
using the Scala binding rule.
So that's probably not what you wanted to do there - the Scala binding rule is very simple, generally quite useful, but not generally expressive.
Regarding your main point, that|x| x + 1
isn't too bad
I bet you'd agree that one of the indicators of elegant code is when an API, class, etc. asks for exactly what it needs to accomplish its task, and no more. E.g. a function shouldn't take a Vec
argument when anything implementing IntoIterator
would do, and it shouldn't take a T
argument when an &T
would have sufficed.
For me, the problem with |x| x + 1
isn't about length, it's about elegance. The language is asking for something it doesn't actually need, in this case the name of the positional argument. Consider again the following example:
users
.iter()
.filter(|x| mypredicate(42, x))
.map(|x| x.name)
.collect();
Ask yourself - What meaning does the x
convey? The answer is nothing - the x
could just as well by y
. This is in contrast to everything else in this expression, which has actual semantic meaning (users
indicates a specific variable, mypredicate
is a specific function, iter
, filter
, etc. are well-known iter methods).
What |x| x + 1
is attempting to convey is simply how a positional argument coming in should be bound to a positional value in the expression. And that is exactly what the _
syntax specifies.
Going general with a binding token (|..|
)
It's possible to provide the language with exactly what it needs, and no more, using binding tokens, e.g. |..|
. The binding token tells you in which scope positional arguments are bound, and the underscores tell you where to put them.
If you wanted full control over where to place the positional arguments, you could adopt a syntax with tokens like _0
, _1
, etc, so you could write things like |..| foo(_1, _0 + _1)
to get |x, y| foo(y, x + y)
.
Final thoughts
If none of this really resonates with you, no worries. I've been writing a lot of OCaml, and I've grown accustomed to pipelined code (very few variables, mostly just functions with syntax specifying how values should flow). Perhaps I just need to make my peace with Rust.