Skip to content
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

Open
wants to merge 8 commits into
base: trunk
Choose a base branch
from
Open

Conversation

CJ-Johnson
Copy link
Contributor

@CJ-Johnson CJ-Johnson commented Apr 3, 2024

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:

@CJ-Johnson CJ-Johnson added the proposal rfc Proposal with request-for-comment sent out label Apr 3, 2024
@github-actions github-actions bot added the proposal A proposal label Apr 3, 2024
@CJ-Johnson CJ-Johnson added proposal draft Proposal in draft, not ready for review proposal rfc Proposal with request-for-comment sent out and removed proposal rfc Proposal with request-for-comment sent out proposal draft Proposal in draft, not ready for review labels Apr 4, 2024
@CJ-Johnson CJ-Johnson marked this pull request as ready for review April 4, 2024 17:56
@github-actions github-actions bot requested a review from chandlerc April 4, 2024 17:56

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.
Copy link
Contributor

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.

Copy link
Contributor Author

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?

Copy link
Contributor

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 Show resolved Hide resolved
proposals/p3848.md Outdated Show resolved Hide resolved
proposals/p3848.md Outdated Show resolved Hide resolved
proposals/p3848.md Outdated Show resolved Hide resolved
proposals/p3848.md Outdated Show resolved Hide resolved
proposals/p3848.md Outdated Show resolved Hide resolved
proposals/p3848.md Outdated Show resolved Hide resolved
proposals/p3848.md Outdated Show resolved Hide resolved
proposals/p3848.md Outdated Show resolved Hide resolved
proposals/p3848.md Outdated Show resolved Hide resolved

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.
Copy link
Contributor

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 Show resolved Hide resolved
proposals/p3848.md Outdated Show resolved Hide resolved
proposals/p3848.md Outdated Show resolved Hide resolved
Comment on lines 521 to 525
**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.
Copy link
Contributor

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:

Suggested change
**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.

proposals/p3848.md Outdated Show resolved Hide resolved
Comment on lines 424 to 425
| `ref` | Capture "by-reference" behaving as a C reference |
| `const ref` | Capture "by-const-reference" behaving as a C const reference |
Copy link
Contributor

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.

Copy link
Contributor

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.

proposals/p3848.md Outdated Show resolved Hide resolved
proposals/p3848.md Outdated Show resolved Hide resolved
proposals/p3848.md Show resolved Hide resolved
proposals/p3848.md Show resolved Hide resolved
github-merge-queue bot pushed a commit that referenced this pull request May 29, 2024
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 <[email protected]>
Comment on lines 386 to 389
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.**
Copy link
Contributor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal rfc Proposal with request-for-comment sent out proposal A proposal
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants