-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Lambdas #3848
base: trunk
Are you sure you want to change the base?
Lambdas #3848
Conversation
|
||
To understand how the syntax between lambdas and function declarations is | ||
reasonably "continuous", refer to this table of syntactic positions and the | ||
following code examples. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider presenting this information more like this, instead:
Function definitions have one of the following syntactic forms (where items in square brackets are optional and independent):
fn
[name] [ implicit-params ] [tuple-pattern]=>
expression;
fn
[name] [ implicit-params ] [tuple-pattern] [->
return-type]{
statements}
The first form is a shorthand for the second: "
=>
expression;
" is equivalent to "-> auto { return
expression; }
".implicit-params consists of square brackets enclosing an optional default capture mode and any number of explicit captures, function fields, and deduced parameters, all separated by commas. The default capture mode (if any) must come first; the other items can appear in any order. If implicit-params is omitted, it is equivalent to
[]
.The presence of name determines whether this is a function declaration or a lambda expression.
The presence of tuple-pattern determines whether the function body uses named or positional parameters.
The presence of "
->
return-type" determines whether the function body can (and must) return a value.
That's more abstract, but at least for me, it would make it much easier to see how this design is (and isn't) continuous.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome! I added this content right above the part that you highlighted. My reading of it is that these two blocks of text are not mutually exclusive. Do you disagree?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree they're not mutually exclusive. Personally I don't find the table and examples helpful, but they may work better for other people.
|
||
To understand how the syntax between lambdas and function declarations is | ||
reasonably "continuous", refer to this table of syntactic positions and the | ||
following code examples. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree they're not mutually exclusive. Personally I don't find the table and examples helpful, but they may work better for other people.
proposals/p3848.md
Outdated
**Proposal**: To mirror the behavior of init captures in C++, function fields | ||
will support nothing-implies-`let` and `var` binding patterns. These will be | ||
annotated with a type and initialized with the right-hand-side of an equals | ||
sign. The lifetime of a function field is the same as the lifetime of the | ||
function declaration or lambda in which it exists. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should specify this slightly differently:
**Proposal**: To mirror the behavior of init captures in C++, function fields | |
will support nothing-implies-`let` and `var` binding patterns. These will be | |
annotated with a type and initialized with the right-hand-side of an equals | |
sign. The lifetime of a function field is the same as the lifetime of the | |
function declaration or lambda in which it exists. | |
**Proposal**: Function fields mirror the behavior of init captures in C++. | |
A function field definition consists of an irrefutable pattern, `=`, and an initializer. | |
It matches the pattern with the initializer when the function definition is evaluated. | |
The bindings in the pattern have the same lifetime as the function, and their scope | |
extends to the end of the function body. |
This is more general than what we've discussed so far, because it allows things like fn [(a: auto, b: auto) = Foo()] {...}
, but that generalization seems desirable.
e119bcb
to
4b408a2
Compare
proposals/p3848.md
Outdated
| `ref` | Capture "by-reference" behaving as a C++ reference | | ||
| `const ref` | Capture "by-const-reference" behaving as a C++ const reference | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suppose I want to write this:
let a: String = "long string I'd rather not make a copy of";
DoThingWithLazyGetter(fn [???] => a);
Can a ref
capture be used to capture a let
binding? If so, what happens -- does that create a temporary and capture a reference to it, or does that capture a value as if by [a: auto = a]
?
If not, I think the outcome is that there isn't a way to capture a let
binding without renaming it. You can use a function field, but our name shadowing rules would suggest that you must use a new name for the function field. I wonder if it'd be worth adding syntax for capturing a value as a value, rather than as an object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we still need an answer here -- I think wanting to capture a local let
binding will be a common case, and requiring it to be renamed seems unsatisfying from an ergonomic perspective.
Parse the name of a declaration as a sequence of `NameQualifier`s -- which have a name, possibly parameters, and a trailing period -- followed by a name and possibly parameters. This prepares us for parsing declarations of members of generic classes and similar cases, but actually supporting such member redeclarations is left to a future change. We previously required functions to have parameters, but no longer do, following the direction of #3848. Cases like namespaces that can't actually have parameters are now diagnosed in check instead of in parse. --------- Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
ca4be05
to
b523652
Compare
b523652
to
2460c5f
Compare
proposals/p3848.md
Outdated
In addition to the proposed restrictions, an additional restriction was | ||
considered. That being, visibility of functions with positional parameters could | ||
be restricted to only non-public interfaces. **This alternative will be put | ||
forth as a leads question before a decision is made.** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This leads question has now been answered: #3860
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me, thanks, and sorry for the slow review cycle!
proposals/p3848.md
Outdated
Much discussion has been had so far about the implications of capturing by | ||
reference. For now, such behavior is supported not through captures but instead | ||
through function fields formed from the address of an object in the outer scope. | ||
It is imperative that more work be done in this area to address the erganomic |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is imperative that more work be done in this area to address the erganomic | |
It is imperative that more work be done in this area to address the ergonomic |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Fixed :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very happy with the direction here. A bunch of comments, but they're all focused on the proposal write up, not the specific proposed feature.
proposals/p3848.md
Outdated
## Abstract | ||
|
||
This document proposes a path forward to add lambdas to Carbon. It further | ||
proposes augmenting function declarations to create a more continuous syntax | ||
between the two categories of functions. In short, both lambdas and function | ||
declarations will be introduced with the `fn` keyword. The presence of a name | ||
distinguishes a declaration from a lambda expression, and the rest of the syntax | ||
applies to both kinds. See [Syntax Overview](#syntax-overview) for more. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a convention of keeping the abstract in the document and the proposal summary th e same -- I feel like there is a bit of content in each, could you merge them?
proposals/p3848.md
Outdated
## Abstract | ||
|
||
This document proposes a path forward to add lambdas to Carbon. It further | ||
proposes augmenting function declarations to create a more continuous syntax | ||
between the two categories of functions. In short, both lambdas and function | ||
declarations will be introduced with the `fn` keyword. The presence of a name | ||
distinguishes a function declaration from a lambda expression, and the rest of | ||
the syntax applies to both kinds. See [Syntax Overview](#syntax-overview) for | ||
more. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably merge the content here and the PR description.
proposals/p3848.md
Outdated
``` | ||
let lambda1: auto = fn => T.Make(); | ||
|
||
let lambda2: auto = fn []() -> T { return T.Make(); }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand this and the below are just illustrating where the []
s and ()
s go, but I suspect we'll want to not accept empty []
s at least... Would it make sense to use some pseudo syntax to indicate that there's something inside? Or to have a more examples?
proposals/p3848.md
Outdated
} | ||
``` | ||
|
||
### Succinctly |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what this section heading means....
(I fear it may be ... too succinct? I'll see myself out.)
proposals/p3848.md
Outdated
The presence of _name_ determines whether this is a function declaration or a | ||
lambda expression. The trailing `;` in the first form is required for a function | ||
declaration, but is not part of the syntax of a lambda expression. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is just trying to speak to definition syntaxes.
The presence of _name_ determines whether this is a function declaration or a | |
lambda expression. The trailing `;` in the first form is required for a function | |
declaration, but is not part of the syntax of a lambda expression. | |
The presence of _name_ determines whether this is a function definition or a | |
lambda expression. The trailing `;` in the first form is required for a function | |
definition, but is not part of the syntax of a lambda expression. |
proposals/p3848.md
Outdated
fn[B, C] => A1 | ||
|
||
fn(D) => A2 | ||
|
||
fn[B, C](D) => A2 | ||
|
||
fn { E1; } | ||
|
||
fn -> F { E2; } | ||
|
||
fn[B, C] { E1; } | ||
|
||
fn[B, C] -> F { E2; } | ||
|
||
fn(D) { E3; } | ||
|
||
fn(D) -> F { E4; } | ||
|
||
fn[B, C](D) { E3; } | ||
|
||
fn[B, C](D) -> F { E4; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding some whitespace based on some bikeshed-y discussion in the syntax channel...
fn[B, C] => A1 | |
fn(D) => A2 | |
fn[B, C](D) => A2 | |
fn { E1; } | |
fn -> F { E2; } | |
fn[B, C] { E1; } | |
fn[B, C] -> F { E2; } | |
fn(D) { E3; } | |
fn(D) -> F { E4; } | |
fn[B, C](D) { E3; } | |
fn[B, C](D) -> F { E4; } | |
fn [B, C] => A1 | |
fn (D) => A2 | |
fn [B, C](D) => A2 | |
fn { E1; } | |
fn -> F { E2; } | |
fn [B, C] { E1; } | |
fn [B, C] -> F { E2; } | |
fn (D) { E3; } | |
fn (D) -> F { E4; } | |
fn [B, C](D) { E3; } | |
fn [B, C](D) -> F { E4; } |
proposals/p3848.md
Outdated
fn G[B, C](D) -> F { E4; } | ||
``` | ||
|
||
### Alternative Considered: Terse vs Elaborated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The alternatives considered are spread throughout the proposal rather than grouped at the bottom which I think makes it a little bit harder to get a sense of what the actual proposal is.
But maybe more importantly, it would be good to try to summarize the pros, cons, and what tipped the scales to not pursue the alternative for each one. That's an important part of the structure we typically want in proposals.
let lambda2: auto = @[]() -> T { return T.Make(); }; | ||
``` | ||
|
||
## Positional Parameters |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I think Swift is one of the few other languages that has a similar feature, it would be good to a) mention that and b) indicate what if any things really differ between them.
proposals/p3848.md
Outdated
To prevent ambiguities, captures can only exist on functions where the | ||
definition is attached to the declaration. This means they are supported on | ||
lambdas and they are supported on function declarations that are immediately | ||
defined, but they are not supported on forward-declared functions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this also mention only being supported inside the body of another function?
Or, alternatively, in an expression (lambda) or statement (function) context?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made this change to both captures and function fields sections :)
Another suggestion for the proposal -- I think it'd be nice to add some comparison for how the Carbon lambdas would look in C++. This doesn't need to get into features that C++ lambdas have and Carbon's don't, just showing how analogous C++ lambdas would look so that folks can compare. |
I'm really liking this. A few small comments. More calling examples, ideally right after the first syntax example. "This would be invoked like this:" kind of thing. I see just one (though I may have missed others) example of a lambda not being put into a variable but being used right away, the comparator in the sort example. This is terrific but may be missed by someone less interested in positional parameters, so some other examples of unnamed lambdas just being used would be great. I would hate for a reader to think that the #1 use of lambdas is to put them into a named variable or value. |
Thanks for the feedback, Kate! I tried to address it by adding both |
This document proposes a path forward to add lambdas to Carbon. It further proposes augmenting function declarations to create a more continuous syntax between the two categories of functions. In short, both lambdas and function declarations will be introduced with the
fn
keyword. The presence of a name distinguishes a function declaration from a lambda expression, and the rest of the syntax applies to both kinds. By providing a valid lambda syntax in Carbon, migration from from C++ to Carbon will be made easier and more idiomatic. In C++, lambdas are defined at their point of use and are often anonymous, meaning replacing them solely with function declarations would create an ergonomic burden compounded by the need for the migration tool to select a name.Associated discussion docs: