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
Function definitions have one of the following syntactic forms (where items in | ||
square brackets are optional and independent): | ||
|
||
`fn` \[_name_\] \[_implicit-parameters_\] \[_tuple-pattern_\] `=>` _expression_ | ||
`;` | ||
|
||
`fn` \[_name_\] \[_implicit-parameters_\] \[_tuple-pattern_\] \[`->` | ||
_return-type_\] `{` _statements_ `}` | ||
|
||
The first form is a shorthand for the second: "`=>` _expression_ `;`" is | ||
equivalent to "`-> auto { return` _expression_ `; }`". | ||
|
||
_implicit-parameters_ consists of square brackets enclosing a 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-parameters_ is | ||
omitted, it is equivalent to `[]`. | ||
|
||
The presence of _name_ determines whether this is a function declaration or 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 we're missing a description of the behavior of the trailing ;
here. How about something like:
Function definitions have one of the following syntactic forms (where items in | |
square brackets are optional and independent): | |
`fn` \[_name_\] \[_implicit-parameters_\] \[_tuple-pattern_\] `=>` _expression_ | |
`;` | |
`fn` \[_name_\] \[_implicit-parameters_\] \[_tuple-pattern_\] \[`->` | |
_return-type_\] `{` _statements_ `}` | |
The first form is a shorthand for the second: "`=>` _expression_ `;`" is | |
equivalent to "`-> auto { return` _expression_ `; }`". | |
_implicit-parameters_ consists of square brackets enclosing a 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-parameters_ is | |
omitted, it is equivalent to `[]`. | |
The presence of _name_ determines whether this is a function declaration or a | |
lambda expression. | |
Function definitions and lambda expressions have one of the following syntactic forms (where items in | |
square brackets are optional and independent): | |
`fn` \[_name_\] \[_implicit-parameters_\] \[_tuple-pattern_\] `=>` _expression_ | |
\[`;`\] | |
`fn` \[_name_\] \[_implicit-parameters_\] \[_tuple-pattern_\] \[`->` | |
_return-type_\] `{` _statements_ `}` | |
The first form is a shorthand for the second: "`=>` _expression_ `;`" is | |
equivalent to "`-> auto { return` _expression_ `; }`". | |
_implicit-parameters_ consists of square brackets enclosing a 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-parameters_ is | |
omitted, it is equivalent to `[]`. | |
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. |
| `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.
The final case is by-value function fields. Since C++ const references, when | ||
made into fields of a class, prevent the class from being copied, so too should | ||
by-value function fields prevent the function in which it is contained from | ||
being copied. |
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 we distinguish between copy construction and copy assignment here? Const reference fields in C++ only prevent copy assignment, not copy construction.
This means that, if a function holds a by-object function field, if the type of | ||
the field is copyable, so too is the function that contains it. This also | ||
applies to by-copy and by-const-copy captures. |
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.
Just to check my understanding:
// Returns the number of times it's been called.
fn Counter[var n: i32 = 0]() { ++n; return n; }
fn Run() {
Print(Counter());
Print(Counter());
var my_counter: auto = Counter;
Print(my_counter());
Print(my_counter());
Print(Counter());
}
... would print 1 2 3 4 3.
be restricted to only non-public interfaces. **This alternative will be put | ||
forth as a leads question before a decision is made.** | ||
|
||
## Function Captures |
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.
Something it might be good to mention here:
If a function object F
has mutable state, either because it has a non-const
copy
capture or because it has a var
function field, then a call to F
should require the callee to be a reference expression rather than a value expression. We need a mutable handle to the function in order to be able to mutate its mutable state.
by-value function fields prevent the function in which it is contained from | ||
being copied. | ||
|
||
## Self and Recursion |
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.
Given the direction in #3720, an expression of the form x.(F)
, where F
is a function with a self
or addr self
parameter, produces a callable that holds the value of x
, and does not hold the value of F
. As a consequence, I think we can't support combining captures and function fields with a self
parameter under that model.
Would that be a reasonable restriction to impose here?
To support migration from C++ to Carbon, there must be valid syntax to capture the behavior of C++ lambdas. They are defined at their point of use and are often anonymous, meaning replacing them solely with function declarations will create an ergonomic burden compounded by the need for the migration tool to select a name. This PR proposes a path forward to add lambdas to Carbon and augment function declarations accordingly.
Associated discussion docs: