-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Implicit closure parameters #2554
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
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 |
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 The use of 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 |
I donno if the parser can manage |
I'm not a fan of the ambiguity around what would be lazily evaluated, eg.
Which of these does it translate to:
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 |
You missed some @Diggsey like
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? 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 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 ( 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 Rust also has the (great) property that overloadable binary operators are available as trait methods, e.g. |
I think there's another good hidden point here too: sometimes one needs to do |
With 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.) |
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 |
Procedural macros for closures with shorthand argument names (like in Swift): 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. |
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:
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.
Pros: The underscore is already used for other things in Rust, most notably generic types and patterns.
Cons:
_
.&_
,&mut _
,*_
Scala's style also lets you do this for closures that take multiple parameters:
which would be the same as
Granted, I feel this is somewhat less readable.
Kotlin Style
Kotlin uses a special variable called
it
Pros:
&it
,&mut it
, and*it
don't look out of placeCons:
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, butit
somehow feels like it makes more sense.The text was updated successfully, but these errors were encountered: