I wanted to collect some of the proposed syntaxes and their pros and cons, in no particular order.
Throughout, we want to write the following with our shorthand syntax.
fn foo(a: i32, b: i32, c: i32) -> i32 { a + b + c }
let f = |c| foo(0, 1, c);
Haskell-style currying
let f = foo(0, 1);
There was brief discussion about introducing curried functions as part of the type system, but that’s outside the scope of this discussion (imo).
Pros:
- No new syntax.
- Limited to partial applications instead of complex expressions.
- Intuitive and natural for users of functional languages.
Cons:
- A partial application can’t be visually distinguished from the full function call.
-
std
and other crates do not have their function parameters ordered to take advantage of this, like in Haskell.
- No good way to call inherent/trait methods, except for UFCS.
Scala-style Wunderbar
let f = foo(0, 1, _);
// or even
let f = 0 + 1 + _;
Though originally proposed for partial function application, it can be extended to arbitrary closures, using semantics like Scala’s.
Pros:
- Uses the pre-existing
_
keyword.
- Consistent with other uses of
_
(in the sense of “I don’t want to name this variable because its name doesn’t matter”).
Cons:
- It’s unclear where the lambda starts in this expression:
foo().bar(_)
. Is foo()
inside the closure, or is its value captured as part of it?
- How do I specify that the output closure should be
move
? move foo(_)
doesn’t work, because now &move foo(_)
is ambiguous.
- What does
foo(_, _)
mean? |y| |x| foo(x, y)
or |x, y| foo(x, y)
?
- Inconsistent with other uses of
_
(where it either stands for “ignore this value” (patterns) or “infer this type/region” (types)). Note: given that it already has two inconsistent uses, I argue that this is not a strong argument against _
.
Scala Wunderbar but with closure bars
let f = || foo(0, 1, _);
let f = |_| foo(0, 1, _);
let f = |..| foo(0, 1, _);
let f = |?| foo(0, 1, _);
Included are some ideas for what should go in the closure bars. (Disclaimer, this is my proposed syntax.)
Pros:
- Solves all but the last con of the regular wunderbar.
- Closure bars are still present when a closure is constructed.
-
|..|
is already proposed as “ignore all parameters passed to this closure”. I think extending it to instead mean "don’t bind any of the parameters by name, but make them accessible through _
" is not
an awful idea (but I’d like feedback on this).
Cons:
-
||
would no longer indicate a nilary closure.
-
|_|
is a strawman, since the _
pattern doesn’t actually introduce bindings and would
just confuse people.
- For single-argument closures,
|..|
is actually a character heavier (I think arguments about character counts are dumb but I’m including this for completeness).
Scala Wunderbar but with a different sigil, with or without closure bars.
let f = foo(0, 1, $);
let f = || foo(0, 1, ?);
let f = |..| foo(0, 1, #);
Pros:
- No mucking about with the meaning of
_
.
Cons:
- New punctuation!
-
let f = #[0];
, for slice access, is ambiguous with attributes (I think?)
- Doesn’t solve any of the other cons of
_
(if we don’t use the bars).
-
$
could be confused with macro captures, and ?
is already mentally linked with error handling.
partial!()
let f = partial!(foo, 0, 1, _);
let f = foo.partial!(0, 1, _);
Pros:
- Pure macro implementation without compiler support.
- Limited to partial application.
Cons:
- WAY more typing, and makes “closure noise” worse.
- No good way to call inherent/trait methods, except for UFCS.
Also, I noted somewhere else in the thread that banning _
in || {..}
might be a good idea, since this syntax is meant for short closures. This idea leads me to want to call this feature “trivial closures” (or some post-bikeshed equivalent), for use where you’d see Python’s single-statement lambda
.