Implicit closure parameters
The current closure style is great. It's simple and to the point and leaves little to be desired.
However, I often find myself writing code like this:
some_operation().and_then(|x| x.get(1));
Where all I'm doing is call an accessor on a single argument. Not having to introduce a single temporary variable for use in short expressions is nice and useful in the case of short in-line expressions.
A few programming languages provide a sort of syntactic sugaring to this pattern, of which I will mention two.
Scala Style
The Scala style of implicit closure parameters is simply to use an underscore.
some_operation().and_then(_.get(1));
Pros: The underscore is already used for other things in Rust, most notably generic types and patterns.
Cons:
- Yet another meaning for
_. - Looks unnatural when referencing and dereferencing:
&_,&mut _,*_
Scala's style also lets you do this for closures that take multiple parameters:
vec.iter().fold(0, _ + _);
which would be the same as
vec.iter().fold(0, |acc, x| acc + x);
Granted, I feel this is somewhat less readable.
Kotlin Style
Kotlin uses a special variable called it
some_operation().and_then(it.get(1));
Pros:
- Doesn't use an underscore, and looks more like natural code.
- Can be used with the existing reference and dereference operators
&it,&mut it, and*itdon't look out of place
Cons:
- New users might be confused where the mysterious variable suddenly appeared from.
- The variable also doesn't have any other uses, so new special rules would have to be applied to this one specific case.
Unlike Scala, Kotlin only uses this to replace a single variable.
This is more of a quality-of-life request moreso than anything, though I still feel it's worth bringing to attention.
Personally I'm split between the two syntaxes, the succinctness of _ is great and quick to type, but it somehow feels like it makes more sense.
While I like succinctness, I prefer the current explicit design for a system language designed for reliability.
Seems related to #1577
I want to reserve _ for value inference (see const generics, etc.) since it is used for inference in other expression contexts but otherwise I think the general idea is nice.
I don’t particularly like either Scala or Kotlin-style syntax for Rust, but the Swift-style syntax I could see:
some_operation.and_then($0.get(1));
vec.iter().fold(0, $0 + $1)
The benefit being that it’s completely unambiguous, there is no implicit mapping of occurrences of _ to the order of arguments. It wouldn’t require making it a keyword or implicit parameter.
The use of $ specifically could work, I think. It might be nice because it resembles the (related) concept of macro substitutions already in the language, or confusing for the same reason.
As far as I know, identifiers in Rust cannot start with a number, so there wouldn’t be any ambiguity in the syntax.
I would prefer something like #0 which seems closer to raw identifier syntax.
I donno if the parser can manage ? but it should probably only be used for error handling. it might likely break too much existing code. Could we get away with .1, .2, etc.? so, vec.iter().fold(0, |acc, x| x + acc); becomes vec.iter().fold(0, .1 + .0);? It's kinda ugly though.
I'm not a fan of the ambiguity around what would be lazily evaluated, eg.
foo(bar($1 + $2) + $1)
Which of these does it translate to:
foo(|x| bar(|a,b| a+b) + x)
foo(|x,y| bar(x+y) + x)
|x,y| foo(bar(x+y)+x)
I think even if we define rules, it will be confusing to read.
I was actually going to mention Elixir's syntax as well, which is something like
vec.iter().fold(0, &1 + &2)
But with it starting on 1 and using & would cause a lot of issues, so I left it out for the sake of the simpler ones.
You missed some @Diggsey like
foo(|x,y| bar(|a| a+y) + x)
foo(|x| bar(|_,b| x+b) + x)
I'm worried this might create a barrier to polymorphic closures.
One could always look at the individual implementations of the other systems and see how they do it.
Maybe that gives some insight to how they got around this mess...
Maybe we should make a new style? Something like
vec.iter().fold(0, .1 + .2);
But actually i'm in love with Scala's closure.
Related: https://internals.rust-lang.org/t/simple-partial-application/7685/22?u=scottmcm
I think I'm far more fond of only doing the one-argument-used-once cases, which don't have the "where's the end of the lambda?" problem as much. And since this is just a short form, there's no need to handle everything.
A testimony: in Swift, we've got unnamed (numeric) closure arguments. Even after having spent 4 years with Swift, I find reading others' code with such closure arguments quite puzzling, apart from the most trivial cases like collection.reduce(0) { $0 + $1 }.
It's not that such code is "bad" or even "complicated". But it quickly becomes hard to parse visually. E.g. there can be a closure containing another closure, which is not at all that uncommon – I've been writing a lot of such code for a fintech company that had its business logic implemented as many functions in the form of 2 or 3 short but nested map-reduce like operations. I can tell you, it's not nice figuring out which arguments belong to which closure ($0 is always the argument of the innermost one, so see two $0s even on the same line, and they might not mean the same thing).
And then there's the changeability aspect too. If you want to capture a closure's unnamed argument, you will now need to go back, give it a name, change its use in every place it was used, and only then can you capture it. (Because of this, I mostly just stopped doing it.) That's way more annoying than having to write |x| x.get(1) once. (And while we are at writing: let me immediately counter my own previous argument by saying that I think this proposal is too biased in favor of ease of writing, whereas we tend to spend more time reading code than writing.)
Rust also has the (great) property that overloadable binary operators are available as trait methods, e.g. Add::add. So the (AFAICT very common) case of just needing the behavior of a single binary operator is already well-served by simply passing the trait method instead of creating an unnecessary closure.
So the (AFAICT very common) case of just needing the behavior of a single binary operator is already well-served by simply passing the trait method instead of creating an unnecessary closure.
I think there's another good hidden point here too: sometimes one needs to do |x, y| x + y to trigger coercions or derefs or similar, so maybe a first step before syntax changes should be more coercions on functions so that one never needs to pass such pointless-seeming closures.
With impl Trait now stable, it should be pretty easy to write combinators such as by_ref or by_deref, something along the lines of:
pub fn by_ref<T, U, F: FnOnce(&T) -> U>(f: F) -> impl FnOnce(T) -> U {
|x| f(&x)
}
pub fn by_deref<T: Copy, U, F: FnOnce(T) -> U>(f: F) -> impl FnOnce(&T) -> U {
|x| f(*x)
}
Before adding any coercions, I'd be interested in achieving this using similar functions. Feels more functional, less ad-hoc, and to be honest, less scary. (If a function is lifted from value-land to ref-land or vice versa, I'd very much like to know it.)
Feels more functional, less ad-hoc, and to be honest, less scary.
Can you elaborate on that feeling? To me it seems like it's weird for
it.map(|x| str::len(x))
to be totally fine when
it.map(str::len)
is a type mismatch error
error[E0631]: type mismatch in function arguments
--> src/lib.rs:2:8
|
2 | it.map(str::len)
| ^^^
| |
| expected signature of `fn(&'a std::string::String) -> _`
| found signature of `for<'r> fn(&'r str) -> _`
Since I didn't call anything different between the two things.
Repro: https://play.rust-lang.org/?gist=94d9c13c639ef0611e6bad92d950ab0b
Sorry, that's not what I meant. I thought you were talking about &T -> T and/or T -> &T coercions, but it's apparently just normal Deref coercions applied in a wider context to allow η-equivalence. That would indeed be nice.
Procedural macros for closures with shorthand argument names (like in Swift): lambda
Example:
Some(3).filter(l!($0 % 2 == 0));
May be useful to someone.
Visual clarity of || is kinda important. Kotlin has the { } indicating the presence of a closure, so does swift. I feel current proposals lack such visual indicators.
I know not needing to write out the || is the point, but it helps with reading the code a bunch
On that note, Kotlin's braces-only closure syntax (and its "scope functions" as the best use of it) would enhance the language substantially while solving this issue. They would mean fewer intermediate variables (cleaning the namespace) and make things that work better as functional patterns (mapping, filtering, matching, error handling) much more concise, greatly enhancing readability and usability all-around, and without sacrificing any performance or adding any ambiguity.
The only problem would be a few intermediate variables now being hidden by the compiler, but that's already done with both matching and macros without issue.
Kotlin's got a lot to learn from Rust, but its mixed imperative/functional constructs are probably the best thing I've used in any language, and Rust could really benefit from them.
https://github.com/SabrinaJewson/implicit-fn.rs