From 1d16340c54dd75c1b8000ac477c12d8be51d6019 Mon Sep 17 00:00:00 2001 From: IvanDuran02 Date: Sun, 13 Apr 2025 02:44:09 -0400 Subject: [PATCH 01/17] Lambdas from proposal to docs/design --- docs/design/lambdas.md | 555 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 555 insertions(+) create mode 100644 docs/design/lambdas.md diff --git a/docs/design/lambdas.md b/docs/design/lambdas.md new file mode 100644 index 0000000000000..a263200af8435 --- /dev/null +++ b/docs/design/lambdas.md @@ -0,0 +1,555 @@ +# Lambdas + + + + + +## Table of contents + +- [Syntax Overview](#syntax-overview) + - [Syntax Defined](#syntax-defined) +- [Introducer](#introducer) +- [Positional Parameters](#positional-parameters) + - [Positional Parameter Restrictions](#positional-parameter-restrictions) +- [Function Captures](#function-captures) + - [Capture Modes](#capture-modes) + - [Default Capture Mode](#default-capture-mode) +- [Function Fields](#function-fields) +- [Copy Semantics](#copy-semantics) +- [Self and Recursion](#self-and-recursion) + + + +## Syntax Overview + +**Proposal**: A largely continuous syntax between lambdas and function +declarations. + +At a high level, lambdas and function declarations will look like the following. + +```carbon +// In a variable: +let lambda: auto = fn => T.Make(); +// Equivalent in C++23: +// const auto lambda = [] { return T::Make(); }; + +// In a function call: +Foo(10, 20, fn => T.Make()); +// Equivalent in C++23: +// Foo(10, 20, [] { return T::Make(); }); +``` + +```carbon +// In a variable: +let lambda: auto = fn -> T { return T.Make(); }; +// Equivalent in C++23: +// const auto lambda = [] -> T { return T::Make(); }; + +// In a function call: +PushBack(my_list, fn => T.Make()); +// Equivalent in C++23: +// PushBack(my_list, [] { return T::Make(); }); +``` + +```carbon +fn FunctionDeclaration => T.Make(); +// Equivalent in C++23: +// auto FunctionDeclaration() { return T.Make(); } +``` + +```carbon +fn FunctionDeclaration -> T { return T.Make(); } +// Equivalent in C++23: +// auto FunctionDeclaration() -> T { return T::Make(); } +``` + +There are functions which return an expression, such that the return type is +`auto`. + +```carbon +// In a variable: +let lambda: auto = fn => T.Make(); +// Equivalent in C++23: +// const auto lambda = [] { return T::Make(); }; + +// In a function call: +Foo(fn => T.Make()); +// Equivalent in C++23: +// Foo([] { return T::Make(); }); +``` + +```carbon +fn FunctionDeclaration => T.Make(); +// Equivalent in C++23: +// auto FunctionDeclaration() { return T::Make(); } +``` + +And there are functions with an explicit return type that provide a body of +statements. + +```carbon +// In a variable: +let lambda: auto = fn -> T { return T.Make(); }; +// Equivalent in C++23: +// const auto lambda = [] -> T { return T::Make(); }; + +// In a function call: +Foo(fn -> T { return T.Make(); }) +// Equivalent in C++23: +// Foo([] -> T { return T::Make(); }); +``` + +```carbon +fn FunctionDeclaration -> T { return T.Make(); } +// Equivalent in C++23: +// auto FunctionDeclaration() -> T { return T::Make(); } +``` + +There are even functions that provide a body of statements but no return value. + +```carbon +// In a variable: +let lambda: auto = fn { Print(T.Make()); }; +// Equivalent in C++23: +// const auto lambda = [] -> void { Print(T::Make()); }; + +// In a function call: +Foo(fn { Print(T.Make()); }); +// Equivalent in C++23: +// Foo([] -> void { Print(T::Make()); }); +``` + +```carbon +fn FunctionDeclaration { Print(T.Make()); } +// Equivalent in C++23: +// auto FunctionDeclaration() -> void { Print(T::Make()); } +``` + +Functions support [captures](#function-captures), [fields](#function-fields) and +deduced parameters in the square brackets. In addition, `self: Self` or +`addr self: Self*` can be added to the square brackets of function declarations +that exist inside class or interface definitions. + +```carbon +fn Foo(x: i32) { + // In a variable: + let lambda: auto = fn [var x, var y: i32 = 0] { Print(++x, ++y); }; + // Equivalent in C++23: + // const auto lambda = [x, y = int32_t{0}] mutable -> void { Print(++x, ++y); }; + + // In a function call: + Foo(fn [var x, var y: i32 = 0] { Print(++x, ++y); }); + // Equivalent in C++23: + // Foo([x, y = int32_t{0}] mutable -> void { Print(++x, ++y); }); + + fn FunctionDeclaration[var x, var y: i32 = 0] { Print(++x, ++y); } + // Equivalent in C++23: + // auto FunctionDeclaration = [x, y = int32_t{0}] mutable -> void { Print(++x, ++y); }; +} +``` + +Functions also support so-called +["positional parameters"](#positional-parameters) that are defined at their +point of use using a dollar sign and a non-negative integer. They are implicitly +of type `auto`. + +```carbon +fn Foo() { + let lambda: auto = fn { Print($0); }; + // Equivalent in C++23: + // auto lambda = [](auto _0, auto...) -> void { Print(_0); }; + // Equivalent in Swift: + // let lambda = { Print($0) }; + + fn FunctionDeclaration { Print($0); } + // Equivalent in C++23: + // auto FunctionDeclaration = [](auto _0, auto...) -> void { Print(_0); }; + // Equivalent in Swift: + // let FunctionDeclaration = { Print($0) }; +} +``` + +Of course, functions can also have named parameters, but a single function can't +have both named and positional parameters. + +```carbon +fn Foo() { + // In a variable: + let lambda: auto = fn (v: auto) { Print(v); }; + // Equivalent in C++23: + // const auto lambda = [](v: auto) -> void { Print(v); }; + + // In a function call: + Foo(fn (v: auto) { Print(v); }); + // Equivalent in C++23: + // Foo([](v: auto) { Print(v); }); + + fn FunctionDeclaration(v: auto) { Print(v); } + // Equivalent in C++23: + // auto FunctionDeclaration(v: auto) -> void { Print(v); } +} +``` + +And in additional the option between positional and named parameters, deduced +parameters are always permitted. + +```carbon +fn Foo() { + let lambda: auto = fn [T:! Printable](t: T) { Print(t); }; + + fn FunctionDeclaration[T:! Printable](t: T) { Print(t); } +} +``` + +### Syntax Defined + +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 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. + +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. + +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. + +| Syntactic Position | Syntax Allowed in Given Position (optional, unless otherwise stated) | +| :----------------: | :----------------------------------------------------------------------------------------------------------------: | +| A1 | Required Returned Expression ([positional parameters](#positional-parameters) allowed) | +| A2 | Required Returned Expression ([positional parameters](#positional-parameters) disallowed) | +| B | [Default Capture Mode](#default-capture-mode) | +| C | Explicit [Captures](#function-captures), [Function Fields](#function-fields) and Deduced Parameters (in any order) | +| D | Explicit Parameters | +| E1 | Body of Statements (no return value) ([positional parameters](#positional-parameters) allowed) | +| E2 | Body of Statements (with return value) ([positional parameters](#positional-parameters) allowed) | +| E3 | Body of Statements (no return value) ([positional parameters](#positional-parameters) disallowed) | +| E4 | Body of Statements (with return value) ([positional parameters](#positional-parameters) disallowed) | +| F | Required Return Type | +| G | Function Declaration Name | + +```carbon +// Lambdas (all the following are in an expression context and are +// themselves expressions) + +fn => A1 + +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; } +``` + +```carbon +// Function Declarations (all the following are allowed as statements in a +// function body or as declarations in other scopes) + +fn G => A1; + +fn G[B, C] => A1; + +fn G(D) => A2; + +fn G[B, C](D) => A2; + +fn G { E1; } + +fn G -> F { E2; } + +fn G[B, C] { E1; } + +fn G[B, C] -> F { E2; } + +fn G(D) { E3; } + +fn G(D) -> F { E4; } + +fn G[B, C](D) { E3; } + +fn G[B, C](D) -> F { E4; } +``` + +## Introducer + +**Proposal**: Introduce with the `fn` keyword to mirror function declarations. +If a statement or declaration begins with `fn`, a name is required and it +becomes a function declaration. Otherwise, if in an expression context, `fn` +introduces a lambda. + +```carbon +let lambda1: auto = fn => T.Make(); + +let lambda2: auto = fn -> T { return T.Make(); }; + +fn FunctionDeclaration1 => T.Make(); + +fn FunctionDeclaration2 -> T { return T.Make(); } +``` + +## Positional Parameters + +**Proposal**: Positional parameters, introduced in the body of a function by way +of the dollar sign and a corresponding non-negative parameter position integer +(ex: `$3`), are `auto` parameters to the function in which they are defined. +They can be used in any lambda or function declaration that lacks an explicit +parameter list (parentheses). They are variadic by design, meaning an unbounded +number of arguments can be passed to any function that lacks an explicit +parameter list. Only the parameters that are named in the body will be read +from, meaning the highest named parameter denotes the minimum number of +arguments required by the function. The function body is free to omit +lower-numbered parameters (ex: `fn { Print($10); }`). + +This syntax was inpsired by Swift's +[Shorthand Argument Names](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/closures/#Shorthand-Argument-Names). + +```swift +// A lambda that takes two positional parameters being used as a comparator +Sort(my_list, fn => $0.val < $1.val); +// In Swift: { $0.val < $1.val } +``` + +### Positional Parameter Restrictions + +**Proposal**: There are two restrictions applied to functions with positional +parameters. The first restriction is that the definitions of function +declarations must be attached to the declarations. The second restriction is +that positional parameters can only be used in a context where there is exactly +one enclosing function without an explicit parameter list. For example... + +```carbon +fn Foo1 { + fn Bar1 {} // ❌ Invalid: Foo1 is already using positional parameters +} + +fn Foo2 { + Print($0); + fn Bar2 {} // ❌ Invalid: Foo2 is already using positional parameters +} + +fn Foo3 { + fn Bar3 { + Print($0); // ❌ Invalid: Foo3 is already using positional parameters + } +} + +fn Foo4() { + fn Bar4 { + Print($0); // ✅ Valid: Foo4 has explicit parameters + } +} + +fn Foo5 { + fn Bar5() {} // ✅ Valid: Bar5 has explicit parameters +} + +fn Foo6() { + my_list.Sort( + fn => $0 < $1 // ✅ Valid: Foo6 has explicit parameters + ); +} +``` + +## Function Captures + +**Proposal**: Function captures in Carbon mirror the non-init captures of C++. A +function capture declaration consists of a capture mode (for `var` captures) +followed by the name of a binding from the enclosing scope, and makes that +identifier available in the inner function body. The lifetime of a capture is +the lifetime of the function in which it exists. For example... + +```carbon +fn Foo() { + let handle: Handle = Handle.Get(); + var thread: Thread = Thread.Make(fn [var handle] { handle.Process(); }); + thread.Join(); +} +``` + +```carbon +fn Foo() { + let handle: Handle = Handle.Get(); + fn MyThread[handle]() { handle.Process(); } + var thread: Thread = Thread.Make(MyThread); + thread.Join(); +} +``` + +### Capture Modes + +**Proposal**: `let` and `var` can appear as function captures. They behave as +they would in regular bindings. + +To prevent ambiguities, captures can only exist on functions where the +definition is attached to the declaration. This means they are supported on +lambdas (which always exist in an expression context) and they are supported on +function declarations that are immediately defined inside the body of another +function (which is in a statement context), but they are not supported on +forward-declared functions nor are they supported as class members where +`self: Self` is permitted. + +Capture modes can be used as +[default capture mode specifiers](#default-capture-mode) or for explicit +captures as shown in the example code below. + +```carbon +fn Example { + var a: i32 = 0; + var b: i32 = 0; + + let lambda: auto = fn [a, var b] { + a += 1; // ❌ Invalid: by-value captures are immutable + + b += 1; // ✅ Valid: Modifies the captured copy of the by-object capture + }; + + lambda(); +} +``` + +```carbon +fn Example { + fn Invalid() -> auto { + var s: String = "Hello world"; + return fn [s]() => s; + } + + // ❌ Invalid: returned lambda references `s` which is no longer alive + // when the lambda is invoked. + Print(Invalid()()); +} +``` + +Note: If a function object F has mutable state, either because it has a +by-object capture or because it has a by-object 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. + +### Default Capture Mode + +**Proposal**: By default, there is no capturing in functions. The lack of any +square brackets is the same as an empty pair of square brackets. Users can opt +into capturing behavior. This is done either by way of individual explicit +captures, or more succinctly by way of a default capture mode. The default +capture mode roughly mirrors the syntax `[=]` and `[&]` capture modes from C++ +by being the first thing to appear in the square brackets. + +```carbon +fn Foo1() { + let handle: Handle = Handle.Get(); + fn MyThread[var]() { + handle.Process(); // `handle` is captured by-object due to the default capture + // mode specifier of `var` + } + var thread: Thread = Thread.Make(MyThread); + thread.Join(); +} + +fn Foo2() { + let handle: Handle = Handle.Get(); + fn MyThread[let]() { + handle.Process(); // `handle` is captured by-value due to the default capture + // mode specifier of `let` + } + var thread: Thread = Thread.Make(MyThread); + thread.Join(); +} +``` + +## Function Fields + +**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. + +To prevent ambiguities, function fields can only exist on functions where the +definition is attached to the declaration. This means they are supported on +lambdas (which always exist in an expression context) and they are supported on +function declarations that are immediately defined inside the body of another +function (which is in a statement context), but they are not supported on +forward-declared functions nor are they supported as class members where +`self: Self` is permitted. + +```carbon +fn Foo() { + var h1: Handle = Handle.Get(); + var h2: Handle = Handle.Get(); + var thread: Thread = Thread.Make(fn [a: auto = h1, var b: auto = h2] { + a.Process(); + b.Process(); + }); + thread.Join(); +} +``` + +## Copy Semantics + +**Proposal**: To mirror the behavior of C++, function declarations and lambdas +will be as copyable as their contained function fields and function captures. +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 captures. + +The other case is by-value function fields. Since C++ const references, when +made into fields of a class, prevent the class from being copied assigned, so +too should by-value function fields prevent the function in which it is +contained from being copied assigned. + +## Self and Recursion + +**Proposal**: To mirror C++'s use of capturing `this`, `self` should always come +from the outer scope as a capture. `self: Self` is never permitted on lambdas. +For function declarations, it is only permitted when the function is a member of +a class type or an interface, such that it refers to the class/interface and not +to the function itself. + +Note: Given the direction in +[#3720](https://github.com/carbon-language/carbon-lang/pull/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, we can't support combining captures and +function fields with a `self` parameter. From 3661763140977af9a5e0537ef54608f5eeee858c Mon Sep 17 00:00:00 2001 From: IvanDuran02 Date: Sun, 13 Apr 2025 02:47:06 -0400 Subject: [PATCH 02/17] Added link to doc/design for lambdas in README.md --- docs/design/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/design/README.md b/docs/design/README.md index 26a0e3a7fa288..c9416403b5cf3 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -3793,7 +3793,10 @@ the critical underpinnings of such abstractions. #### Lambdas -> **TODO:** +> **TODO:** References need to be evolved. Needs a detailed design and a high +> level summary provided inline. + +> References: [Lambdas](lambdas.md) #### Co-routines From 5eac459cc8729161bf764d08eb4a658fb0a22da0 Mon Sep 17 00:00:00 2001 From: IvanDuran02 Date: Sat, 19 Apr 2025 04:36:47 -0400 Subject: [PATCH 03/17] Updates to wording to fit lambda context --- docs/design/README.md | 2 +- docs/design/lambdas.md | 299 ++++++++++++++++------------------------- 2 files changed, 118 insertions(+), 183 deletions(-) diff --git a/docs/design/README.md b/docs/design/README.md index c9416403b5cf3..03d0e13727ad8 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -3796,7 +3796,7 @@ the critical underpinnings of such abstractions. > **TODO:** References need to be evolved. Needs a detailed design and a high > level summary provided inline. -> References: [Lambdas](lambdas.md) +> References: [Lambdas](lambdas.md) [Proposal](https://github.com/carbon-language/carbon-lang/pull/3848) #### Co-routines diff --git a/docs/design/lambdas.md b/docs/design/lambdas.md index a263200af8435..6de354939908e 100644 --- a/docs/design/lambdas.md +++ b/docs/design/lambdas.md @@ -11,11 +11,16 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ## Table of contents - [Syntax Overview](#syntax-overview) + - [Return type](#return-type) + - [Return expression](#return-expression) + - [Explicit return type](#explicit-return-type) + - [No return](#no-return) + - [Implicit parameters in square brackets](#implicit-parameters-in-square-brackets) + - [Parameters](#parameters) - [Syntax Defined](#syntax-defined) -- [Introducer](#introducer) - [Positional Parameters](#positional-parameters) - [Positional Parameter Restrictions](#positional-parameter-restrictions) -- [Function Captures](#function-captures) +- [Lambda Captures](#lambda-captures) - [Capture Modes](#capture-modes) - [Default Capture Mode](#default-capture-mode) - [Function Fields](#function-fields) @@ -26,10 +31,10 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ## Syntax Overview -**Proposal**: A largely continuous syntax between lambdas and function -declarations. +One goal of Carbon's lambda syntax is to have continuity between lambdas and function. +Below are some example declarations: -At a high level, lambdas and function declarations will look like the following. +Implicit return types ```carbon // In a variable: @@ -37,59 +42,55 @@ let lambda: auto = fn => T.Make(); // Equivalent in C++23: // const auto lambda = [] { return T::Make(); }; -// In a function call: -Foo(10, 20, fn => T.Make()); +// As an argument to a function call: +Foo(10, E20, fn => T.Make()); // Equivalent in C++23: // Foo(10, 20, [] { return T::Make(); }); ``` +Explicit return types + ```carbon // In a variable: let lambda: auto = fn -> T { return T.Make(); }; // Equivalent in C++23: // const auto lambda = [] -> T { return T::Make(); }; -// In a function call: -PushBack(my_list, fn => T.Make()); +// As an argument to a function call: +PushBack(my_list, fn -> T { return T.Make() }); // Equivalent in C++23: // PushBack(my_list, [] { return T::Make(); }); ``` -```carbon -fn FunctionDeclaration => T.Make(); -// Equivalent in C++23: -// auto FunctionDeclaration() { return T.Make(); } -``` +### Return type -```carbon -fn FunctionDeclaration -> T { return T.Make(); } -// Equivalent in C++23: -// auto FunctionDeclaration() -> T { return T::Make(); } -``` +There are three options for how a lambda expresses its return type, parallel to +[how function declarations express returns](functions.md#return-clause): using +a return expression, using an explicit return type, or having no return. -There are functions which return an expression, such that the return type is -`auto`. +#### Return expression -```carbon +A return expression is introduced with a double arrow (`=>`) followed by an +expression describing the function's return value. In this case, the return type is +determined by the type of the expression, as if the return type was `auto`. + +```carbon // In a variable: let lambda: auto = fn => T.Make(); // Equivalent in C++23: // const auto lambda = [] { return T::Make(); }; -// In a function call: +// As an argument to a function call: Foo(fn => T.Make()); // Equivalent in C++23: // Foo([] { return T::Make(); }); ``` -```carbon -fn FunctionDeclaration => T.Make(); -// Equivalent in C++23: -// auto FunctionDeclaration() { return T::Make(); } -``` +#### Explicit return type -And there are functions with an explicit return type that provide a body of -statements. +An explicit return type is introduced with a single arrow (`->`), followed by the +return type, and finally the body of the lambda with a sequence of statements +enclosed in curly braces (`{`...`}`). ```carbon // In a variable: @@ -97,19 +98,16 @@ let lambda: auto = fn -> T { return T.Make(); }; // Equivalent in C++23: // const auto lambda = [] -> T { return T::Make(); }; -// In a function call: +// As an argument to a function call: Foo(fn -> T { return T.Make(); }) // Equivalent in C++23: // Foo([] -> T { return T::Make(); }); ``` -```carbon -fn FunctionDeclaration -> T { return T.Make(); } -// Equivalent in C++23: -// auto FunctionDeclaration() -> T { return T::Make(); } -``` +#### No return -There are even functions that provide a body of statements but no return value. +Lambdas that don't return anything end with a body of statements in curly braces +(`{`...`}`). ```carbon // In a variable: @@ -117,22 +115,16 @@ let lambda: auto = fn { Print(T.Make()); }; // Equivalent in C++23: // const auto lambda = [] -> void { Print(T::Make()); }; -// In a function call: +// As an argument in a function call: Foo(fn { Print(T.Make()); }); // Equivalent in C++23: // Foo([] -> void { Print(T::Make()); }); ``` -```carbon -fn FunctionDeclaration { Print(T.Make()); } -// Equivalent in C++23: -// auto FunctionDeclaration() -> void { Print(T::Make()); } -``` +### Implicit parameters in square brackets -Functions support [captures](#function-captures), [fields](#function-fields) and -deduced parameters in the square brackets. In addition, `self: Self` or -`addr self: Self*` can be added to the square brackets of function declarations -that exist inside class or interface definitions. +Lambdas support [captures](#function-captures), [fields](#function-fields) and +deduced parameters in the square brackets. ```carbon fn Foo(x: i32) { @@ -141,21 +133,18 @@ fn Foo(x: i32) { // Equivalent in C++23: // const auto lambda = [x, y = int32_t{0}] mutable -> void { Print(++x, ++y); }; - // In a function call: + // As an argument in a function calll: Foo(fn [var x, var y: i32 = 0] { Print(++x, ++y); }); // Equivalent in C++23: // Foo([x, y = int32_t{0}] mutable -> void { Print(++x, ++y); }); - - fn FunctionDeclaration[var x, var y: i32 = 0] { Print(++x, ++y); } - // Equivalent in C++23: - // auto FunctionDeclaration = [x, y = int32_t{0}] mutable -> void { Print(++x, ++y); }; } ``` -Functions also support so-called -["positional parameters"](#positional-parameters) that are defined at their -point of use using a dollar sign and a non-negative integer. They are implicitly -of type `auto`. +### Parameters + +Lambdas also support so-called ["positional parameters"](#positional-parameters) +that are defined at their point of use using a dollar sign and a non-negative integer. +They are implicitly of type `auto`. ```carbon fn Foo() { @@ -164,16 +153,10 @@ fn Foo() { // auto lambda = [](auto _0, auto...) -> void { Print(_0); }; // Equivalent in Swift: // let lambda = { Print($0) }; - - fn FunctionDeclaration { Print($0); } - // Equivalent in C++23: - // auto FunctionDeclaration = [](auto _0, auto...) -> void { Print(_0); }; - // Equivalent in Swift: - // let FunctionDeclaration = { Print($0) }; } ``` -Of course, functions can also have named parameters, but a single function can't +Of course, lambdas can also have named parameters, but a single lambda can't have both named and positional parameters. ```carbon @@ -183,14 +166,10 @@ fn Foo() { // Equivalent in C++23: // const auto lambda = [](v: auto) -> void { Print(v); }; - // In a function call: + // As an argument to a function call: Foo(fn (v: auto) { Print(v); }); // Equivalent in C++23: // Foo([](v: auto) { Print(v); }); - - fn FunctionDeclaration(v: auto) { Print(v); } - // Equivalent in C++23: - // auto FunctionDeclaration(v: auto) -> void { Print(v); } } ``` @@ -200,23 +179,20 @@ parameters are always permitted. ```carbon fn Foo() { let lambda: auto = fn [T:! Printable](t: T) { Print(t); }; - - fn FunctionDeclaration[T:! Printable](t: T) { Print(t); } } ``` ### Syntax Defined -Function definitions and lambda expressions have one of the following syntactic +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`\[_implicit-parameters_\] \[_tuple-pattern_\] `=>` _expression_ -`fn` \[_name_\] \[_implicit-parameters_\] \[_tuple-pattern_\] \[`->` +`fn` \[_implicit-parameters_\] \[_tuple-pattern_\] \[`->` _return-type_\] `{` _statements_ `}` -The first form is a shorthand for the second: "`=>` _expression_ `;`" is +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 @@ -225,9 +201,8 @@ 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 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. +Function definitions are distinguished from lambdas by the presence of a name +after the `fn` keyword. The presence of _tuple-pattern_ determines whether the function body uses named or positional parameters. @@ -251,7 +226,6 @@ following code examples. | E3 | Body of Statements (no return value) ([positional parameters](#positional-parameters) disallowed) | | E4 | Body of Statements (with return value) ([positional parameters](#positional-parameters) disallowed) | | F | Required Return Type | -| G | Function Declaration Name | ```carbon // Lambdas (all the following are in an expression context and are @@ -282,69 +256,27 @@ fn [B, C](D) { E3; } fn [B, C](D) -> F { E4; } ``` -```carbon -// Function Declarations (all the following are allowed as statements in a -// function body or as declarations in other scopes) - -fn G => A1; - -fn G[B, C] => A1; - -fn G(D) => A2; - -fn G[B, C](D) => A2; - -fn G { E1; } - -fn G -> F { E2; } - -fn G[B, C] { E1; } - -fn G[B, C] -> F { E2; } - -fn G(D) { E3; } - -fn G(D) -> F { E4; } - -fn G[B, C](D) { E3; } - -fn G[B, C](D) -> F { E4; } -``` - -## Introducer +## Positional Parameters -**Proposal**: Introduce with the `fn` keyword to mirror function declarations. -If a statement or declaration begins with `fn`, a name is required and it -becomes a function declaration. Otherwise, if in an expression context, `fn` -introduces a lambda. +Positional parameters, denoted by a dollar sign followed by a non-negative +integer (e.g., $3), are auto-typed parameters defined within the lambda's body. ```carbon -let lambda1: auto = fn => T.Make(); - -let lambda2: auto = fn -> T { return T.Make(); }; - -fn FunctionDeclaration1 => T.Make(); - -fn FunctionDeclaration2 -> T { return T.Make(); } +let lambda: auto = fn => { return $0 }; ``` -## Positional Parameters - -**Proposal**: Positional parameters, introduced in the body of a function by way -of the dollar sign and a corresponding non-negative parameter position integer -(ex: `$3`), are `auto` parameters to the function in which they are defined. -They can be used in any lambda or function declaration that lacks an explicit +They can be used in any lambda declaration that lacks an explicit parameter list (parentheses). They are variadic by design, meaning an unbounded number of arguments can be passed to any function that lacks an explicit parameter list. Only the parameters that are named in the body will be read from, meaning the highest named parameter denotes the minimum number of -arguments required by the function. The function body is free to omit +arguments required by the function. The lambda body is free to omit lower-numbered parameters (ex: `fn { Print($10); }`). This syntax was inpsired by Swift's [Shorthand Argument Names](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/closures/#Shorthand-Argument-Names). -```swift +```carbon // A lambda that takes two positional parameters being used as a comparator Sort(my_list, fn => $0.val < $1.val); // In Swift: { $0.val < $1.val } @@ -352,49 +284,34 @@ Sort(my_list, fn => $0.val < $1.val); ### Positional Parameter Restrictions -**Proposal**: There are two restrictions applied to functions with positional -parameters. The first restriction is that the definitions of function -declarations must be attached to the declarations. The second restriction is -that positional parameters can only be used in a context where there is exactly -one enclosing function without an explicit parameter list. For example... +There are two restrictions applied to functions with positional +parameters. The first restriction is that the function's definition must be +attached to the declaration. The second restriction is that positional +parameters can only be used in a context where there is exactly +one enclosing function that has no explicit parameter list. For example... ```carbon fn Foo1 { - fn Bar1 {} // ❌ Invalid: Foo1 is already using positional parameters + let lambda: auto = fn => ( $0 < $1 ) // ✅ Valid: Foo1 has no parameter list } fn Foo2 { - Print($0); - fn Bar2 {} // ❌ Invalid: Foo2 is already using positional parameters -} - -fn Foo3 { - fn Bar3 { - Print($0); // ❌ Invalid: Foo3 is already using positional parameters - } -} - -fn Foo4() { - fn Bar4 { - Print($0); // ✅ Valid: Foo4 has explicit parameters - } -} - -fn Foo5 { - fn Bar5() {} // ✅ Valid: Bar5 has explicit parameters + my_list.Sort( + fn => { $0 < $1 } // ❌ Invalid: Foo2 is already using positional parameters + ); } -fn Foo6() { +fn Foo3() { my_list.Sort( - fn => $0 < $1 // ✅ Valid: Foo6 has explicit parameters + fn => { $0 < $1 } // ✅ Valid: Foo3 has explicit parameters ); } ``` -## Function Captures +## Lambda Captures -**Proposal**: Function captures in Carbon mirror the non-init captures of C++. A -function capture declaration consists of a capture mode (for `var` captures) +Lambda captures in Carbon mirror the non-init captures of C++. A + capture declaration consists of a capture mode (for `var` captures) followed by the name of a binding from the enclosing scope, and makes that identifier available in the inner function body. The lifetime of a capture is the lifetime of the function in which it exists. For example... @@ -418,16 +335,31 @@ fn Foo() { ### Capture Modes -**Proposal**: `let` and `var` can appear as function captures. They behave as -they would in regular bindings. +Lambdas can capture variables from their surrounding scope using `let` or `var`, +just like regular bindings. These capture modes determine whether the lambda +sees a copy (`let`) or a mutable reference (`var`) to the captured variable. -To prevent ambiguities, captures can only exist on functions where the -definition is attached to the declaration. This means they are supported on -lambdas (which always exist in an expression context) and they are supported on -function declarations that are immediately defined inside the body of another -function (which is in a statement context), but they are not supported on -forward-declared functions nor are they supported as class members where -`self: Self` is permitted. +To avoid ambiguity, captures are only allowed when the lambda is **defined +inline**—within an expression or directly inside a function body. This means: + +- ✅ Lambdas support captures. +- ✅ Nested lambdas defined inside a function body support captures. +- ❌ Lambdas or functions declared ahead of time (e.g., forward-declared) + **cannot** use captures. +- ❌ Captures are not allowed in class member functions where `self: Self` is + used. + +You can use capture modes in two ways: +- As **default capture mode specifiers** +- For **explicit captures**, as shown in the example below: + +```carbon +fn Foo() { + var counter: i32 = 0; + let printer = fn [let counter] { Print(counter); }; + let incrementer = fn [var counter] { counter += 1; }; +} +``` Capture modes can be used as [default capture mode specifiers](#default-capture-mode) or for explicit @@ -438,11 +370,10 @@ fn Example { var a: i32 = 0; var b: i32 = 0; - let lambda: auto = fn [a, var b] { - a += 1; // ❌ Invalid: by-value captures are immutable - - b += 1; // ✅ Valid: Modifies the captured copy of the by-object capture - }; +let lambda: auto = fn [a, var b] { + a += 1; // ❌ Invalid: `a` is an immutable copy (default `let`) + b += 1; // ✅ Valid: `b` is a mutable copy (captured with `var`) +}; lambda(); } @@ -461,15 +392,13 @@ fn Example { } ``` -Note: If a function object F has mutable state, either because it has a -by-object capture or because it has a by-object 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. +Note: If a function object F has mutable state (via by-object captures or +mutable fields), calling F requires a mutable reference. This ensures the +function has access to its internal state for mutation. ### Default Capture Mode -**Proposal**: By default, there is no capturing in functions. The lack of any +By default, there is no capturing in lambdas. The lack of any square brackets is the same as an empty pair of square brackets. Users can opt into capturing behavior. This is done either by way of individual explicit captures, or more succinctly by way of a default capture mode. The default @@ -498,11 +427,11 @@ fn Foo2() { } ``` -## Function Fields +## Function Fields in Lambdas -**Proposal**: Function fields mirror the behavior of init captures in C++. A +Function fields in lambdas 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 +initializer. It matches the pattern with the initializer when the lambda 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. @@ -528,7 +457,7 @@ fn Foo() { ## Copy Semantics -**Proposal**: To mirror the behavior of C++, function declarations and lambdas +To mirror the behavior of C++, function declarations and lambdas will be as copyable as their contained function fields and function captures. 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 @@ -541,12 +470,18 @@ contained from being copied assigned. ## Self and Recursion -**Proposal**: To mirror C++'s use of capturing `this`, `self` should always come +To mirror C++'s use of capturing `this`, `self` should always come from the outer scope as a capture. `self: Self` is never permitted on lambdas. For function declarations, it is only permitted when the function is a member of a class type or an interface, such that it refers to the class/interface and not to the function itself. +```carbon +fn = [self: Self] { return self.F(); }; // ❌ Not allowed + +fn = [self] { return self.F(); }; // ✅ Only if self came from outside +``` + Note: Given the direction in [#3720](https://github.com/carbon-language/carbon-lang/pull/3720), an expression of the form `x.(F)`, where `F` is a function with a `self` or `addr self` From 37ff0d911730e1e7e90a9d3b476bcc3f56e56923 Mon Sep 17 00:00:00 2001 From: IvanDuran02 Date: Sat, 19 Apr 2025 04:43:26 -0400 Subject: [PATCH 04/17] self and recursion update --- docs/design/lambdas.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/docs/design/lambdas.md b/docs/design/lambdas.md index 6de354939908e..f7666a4c8ccea 100644 --- a/docs/design/lambdas.md +++ b/docs/design/lambdas.md @@ -457,11 +457,10 @@ fn Foo() { ## Copy Semantics -To mirror the behavior of C++, function declarations and lambdas -will be as copyable as their contained function fields and function captures. -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 captures. +To mirror the behavior of C++, lambdas will be as copyable as their contained +function fields and function captures. 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 captures. The other case is by-value function fields. Since C++ const references, when made into fields of a class, prevent the class from being copied assigned, so @@ -472,9 +471,6 @@ contained from being copied assigned. To mirror C++'s use of capturing `this`, `self` should always come from the outer scope as a capture. `self: Self` is never permitted on lambdas. -For function declarations, it is only permitted when the function is a member of -a class type or an interface, such that it refers to the class/interface and not -to the function itself. ```carbon fn = [self: Self] { return self.F(); }; // ❌ Not allowed From 9133ee3cedfb9c3b952f74631e482572b2d7bb23 Mon Sep 17 00:00:00 2001 From: Ivan Duran Date: Mon, 21 Apr 2025 21:41:47 -0400 Subject: [PATCH 05/17] Update docs/design/lambdas.md Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com> --- docs/design/lambdas.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/lambdas.md b/docs/design/lambdas.md index f7666a4c8ccea..d1d4ed0c15d4a 100644 --- a/docs/design/lambdas.md +++ b/docs/design/lambdas.md @@ -478,7 +478,7 @@ fn = [self: Self] { return self.F(); }; // ❌ Not allowed fn = [self] { return self.F(); }; // ✅ Only if self came from outside ``` -Note: Given the direction in +Note: Following [#3720](https://github.com/carbon-language/carbon-lang/pull/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 From 7e7a320b90f7e532599ff2c673776a29a9a0c2cd Mon Sep 17 00:00:00 2001 From: Ivan Duran Date: Mon, 21 Apr 2025 21:42:10 -0400 Subject: [PATCH 06/17] Update docs/design/README.md Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com> --- docs/design/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/README.md b/docs/design/README.md index 03d0e13727ad8..9a6cab57bdcd8 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -3796,7 +3796,7 @@ the critical underpinnings of such abstractions. > **TODO:** References need to be evolved. Needs a detailed design and a high > level summary provided inline. -> References: [Lambdas](lambdas.md) [Proposal](https://github.com/carbon-language/carbon-lang/pull/3848) +> References: [Lambdas](lambdas.md), [Proposal #3848: Lambdas](https://github.com/carbon-language/carbon-lang/pull/3848) #### Co-routines From 4073ce75d5f489cbf15b1cc781a8d88eb8277d92 Mon Sep 17 00:00:00 2001 From: Ivan Duran Date: Mon, 21 Apr 2025 21:42:48 -0400 Subject: [PATCH 07/17] Update docs/design/lambdas.md Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com> --- docs/design/lambdas.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/lambdas.md b/docs/design/lambdas.md index d1d4ed0c15d4a..ab408242f925f 100644 --- a/docs/design/lambdas.md +++ b/docs/design/lambdas.md @@ -115,7 +115,7 @@ let lambda: auto = fn { Print(T.Make()); }; // Equivalent in C++23: // const auto lambda = [] -> void { Print(T::Make()); }; -// As an argument in a function call: +// As an argument to a function call: Foo(fn { Print(T.Make()); }); // Equivalent in C++23: // Foo([] -> void { Print(T::Make()); }); From 13a0118c0a7ade06c94b7593cd7c999c209d46d5 Mon Sep 17 00:00:00 2001 From: Ivan Duran Date: Mon, 21 Apr 2025 21:42:56 -0400 Subject: [PATCH 08/17] Update docs/design/lambdas.md Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com> --- docs/design/lambdas.md | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/docs/design/lambdas.md b/docs/design/lambdas.md index ab408242f925f..2fceb209dcb4d 100644 --- a/docs/design/lambdas.md +++ b/docs/design/lambdas.md @@ -339,27 +339,6 @@ Lambdas can capture variables from their surrounding scope using `let` or `var`, just like regular bindings. These capture modes determine whether the lambda sees a copy (`let`) or a mutable reference (`var`) to the captured variable. -To avoid ambiguity, captures are only allowed when the lambda is **defined -inline**—within an expression or directly inside a function body. This means: - -- ✅ Lambdas support captures. -- ✅ Nested lambdas defined inside a function body support captures. -- ❌ Lambdas or functions declared ahead of time (e.g., forward-declared) - **cannot** use captures. -- ❌ Captures are not allowed in class member functions where `self: Self` is - used. - -You can use capture modes in two ways: -- As **default capture mode specifiers** -- For **explicit captures**, as shown in the example below: - -```carbon -fn Foo() { - var counter: i32 = 0; - let printer = fn [let counter] { Print(counter); }; - let incrementer = fn [var counter] { counter += 1; }; -} -``` Capture modes can be used as [default capture mode specifiers](#default-capture-mode) or for explicit From fa5a9e8190755e2274f515b2c46bcbbd78297b64 Mon Sep 17 00:00:00 2001 From: Ivan Duran Date: Mon, 21 Apr 2025 21:43:22 -0400 Subject: [PATCH 09/17] Update docs/design/lambdas.md Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com> --- docs/design/lambdas.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/design/lambdas.md b/docs/design/lambdas.md index 2fceb209dcb4d..fd4ae58cfaaca 100644 --- a/docs/design/lambdas.md +++ b/docs/design/lambdas.md @@ -371,9 +371,11 @@ fn Example { } ``` -Note: If a function object F has mutable state (via by-object captures or -mutable fields), calling F requires a mutable reference. This ensures the -function has access to its internal state for mutation. +Note: If a function object F has mutable state, either because it has a +by-object capture or because it has a by-object 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. ### Default Capture Mode From 689c4c318906a4d8fb45da415a87443a786c9c18 Mon Sep 17 00:00:00 2001 From: Ivan Duran Date: Mon, 21 Apr 2025 21:43:37 -0400 Subject: [PATCH 10/17] Update docs/design/lambdas.md Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com> --- docs/design/lambdas.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/design/lambdas.md b/docs/design/lambdas.md index fd4ae58cfaaca..ce7872b7139f2 100644 --- a/docs/design/lambdas.md +++ b/docs/design/lambdas.md @@ -416,14 +416,6 @@ initializer. It matches the pattern with the initializer when the lambda 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. -To prevent ambiguities, function fields can only exist on functions where the -definition is attached to the declaration. This means they are supported on -lambdas (which always exist in an expression context) and they are supported on -function declarations that are immediately defined inside the body of another -function (which is in a statement context), but they are not supported on -forward-declared functions nor are they supported as class members where -`self: Self` is permitted. - ```carbon fn Foo() { var h1: Handle = Handle.Get(); From 163df17f0ed7243db7966fe824d97506cc90c5a1 Mon Sep 17 00:00:00 2001 From: Ivan Duran Date: Mon, 21 Apr 2025 21:43:56 -0400 Subject: [PATCH 11/17] Update docs/design/lambdas.md Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com> --- docs/design/lambdas.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/design/lambdas.md b/docs/design/lambdas.md index ce7872b7139f2..9ff0ee58ef419 100644 --- a/docs/design/lambdas.md +++ b/docs/design/lambdas.md @@ -446,7 +446,8 @@ To mirror C++'s use of capturing `this`, `self` should always come from the outer scope as a capture. `self: Self` is never permitted on lambdas. ```carbon -fn = [self: Self] { return self.F(); }; // ❌ Not allowed +// ❌ Not allowed +let lambda: auto = fn [self: Self] { self.F(); }; fn = [self] { return self.F(); }; // ✅ Only if self came from outside ``` From dda3b1a7ddb7134920dc04d3cfc10aef9f94fb9b Mon Sep 17 00:00:00 2001 From: Ivan Duran Date: Mon, 21 Apr 2025 21:44:19 -0400 Subject: [PATCH 12/17] Update docs/design/lambdas.md Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com> --- docs/design/lambdas.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/design/lambdas.md b/docs/design/lambdas.md index 9ff0ee58ef419..839941a3c6d41 100644 --- a/docs/design/lambdas.md +++ b/docs/design/lambdas.md @@ -449,7 +449,8 @@ from the outer scope as a capture. `self: Self` is never permitted on lambdas. // ❌ Not allowed let lambda: auto = fn [self: Self] { self.F(); }; -fn = [self] { return self.F(); }; // ✅ Only if self came from outside +// ✅ Captures `self` from outer scope +let lambda: auto = fn [self] { self.F(); }; ``` Note: Following From cb6ba39b3b612620335d5de5f4a58d14a7622260 Mon Sep 17 00:00:00 2001 From: Ivan Duran Date: Tue, 22 Apr 2025 01:27:05 -0400 Subject: [PATCH 13/17] Update docs/design/lambdas.md Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com> --- docs/design/lambdas.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/lambdas.md b/docs/design/lambdas.md index 839941a3c6d41..d41e90cef408d 100644 --- a/docs/design/lambdas.md +++ b/docs/design/lambdas.md @@ -31,7 +31,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ## Syntax Overview -One goal of Carbon's lambda syntax is to have continuity between lambdas and function. +One goal of Carbon's lambda syntax is to have continuity between lambdas and function declarations. Below are some example declarations: Implicit return types From 73197e44647ff0ccf912ae9f93631bd8cb261f67 Mon Sep 17 00:00:00 2001 From: Ivan Duran Date: Tue, 22 Apr 2025 01:30:30 -0400 Subject: [PATCH 14/17] Apply suggestions from code review Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com> --- docs/design/lambdas.md | 45 ++++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/docs/design/lambdas.md b/docs/design/lambdas.md index d41e90cef408d..cddae250aa094 100644 --- a/docs/design/lambdas.md +++ b/docs/design/lambdas.md @@ -284,33 +284,46 @@ Sort(my_list, fn => $0.val < $1.val); ### Positional Parameter Restrictions -There are two restrictions applied to functions with positional -parameters. The first restriction is that the function's definition must be -attached to the declaration. The second restriction is that positional -parameters can only be used in a context where there is exactly -one enclosing function that has no explicit parameter list. For example... +Lambdas with positional parameters have the restriction that they +can only be used in a context where there is exactly one enclosing +function or lambda that has no explicit parameter list. For example: ```carbon fn Foo1 { - let lambda: auto = fn => ( $0 < $1 ) // ✅ Valid: Foo1 has no parameter list + // ❌ Invalid: Foo1 is already using positional parameters + let lambda: auto = fn => ( $0 < $1 ) } fn Foo2 { my_list.Sort( - fn => { $0 < $1 } // ❌ Invalid: Foo2 is already using positional parameters + fn => $0 < $1 // ❌ Invalid: Foo2 is already using positional parameters ); } fn Foo3() { my_list.Sort( - fn => { $0 < $1 } // ✅ Valid: Foo3 has explicit parameters + fn => $0 < $1 // ✅ Valid: Foo3 has explicit parameters ); } + +fn Foo4() { + let lambda: auto = fn -> bool { + // ❌ Invalid: Outer lambda is already using positional parameters + return (fn => $0 < $1)($0, $1); + }; +} + +fn Foo5() { + let lambda: auto = fn (x: i32, y: i32) -> bool { + // ✅ Valid: Outer lambda has explicit parameters + return (fn => $0 < $1)(x, y); + }; +} ``` -## Lambda Captures +## Captures -Lambda captures in Carbon mirror the non-init captures of C++. A +Captures in Carbon mirror the non-init captures of C++. A capture declaration consists of a capture mode (for `var` captures) followed by the name of a binding from the enclosing scope, and makes that identifier available in the inner function body. The lifetime of a capture is @@ -336,8 +349,7 @@ fn Foo() { ### Capture Modes Lambdas can capture variables from their surrounding scope using `let` or `var`, -just like regular bindings. These capture modes determine whether the lambda -sees a copy (`let`) or a mutable reference (`var`) to the captured variable. +just like regular bindings. Capture modes can be used as @@ -350,7 +362,7 @@ fn Example { var b: i32 = 0; let lambda: auto = fn [a, var b] { - a += 1; // ❌ Invalid: `a` is an immutable copy (default `let`) + a += 1; // ❌ Invalid: by-value captures are immutable (default `let`) b += 1; // ✅ Valid: `b` is a mutable copy (captured with `var`) }; @@ -459,3 +471,10 @@ 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, we can't support combining captures and function fields with a `self` parameter. + +## Alternatives considered + +- [Terse vs Elaborated](/proposals/p3848.md#alternative-considered-terse-vs-elaborated) +- [Sigil](/proposals/p3848.md#alternative-considered-sigil) +- [Additional Positional Parameter Restriction](/proposals/p3848.md#alternative-considered-additional-positional-parameter-restriction) +- [Recursive Self](/proposals/p3848.md#alternative-considered-recursive-self) From 31f903a4aef555388b30f7cff22fd1924e10a69b Mon Sep 17 00:00:00 2001 From: Ivan Duran Date: Tue, 22 Apr 2025 01:37:59 -0400 Subject: [PATCH 15/17] Update docs/design/lambdas.md Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com> --- docs/design/lambdas.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/lambdas.md b/docs/design/lambdas.md index cddae250aa094..7f69ba5279ce8 100644 --- a/docs/design/lambdas.md +++ b/docs/design/lambdas.md @@ -133,7 +133,7 @@ fn Foo(x: i32) { // Equivalent in C++23: // const auto lambda = [x, y = int32_t{0}] mutable -> void { Print(++x, ++y); }; - // As an argument in a function calll: + // As an argument to a function calll: Foo(fn [var x, var y: i32 = 0] { Print(++x, ++y); }); // Equivalent in C++23: // Foo([x, y = int32_t{0}] mutable -> void { Print(++x, ++y); }); From b453745f9a844114b4cfc63eb2ebc8524b3c7dab Mon Sep 17 00:00:00 2001 From: IvanDuran02 Date: Tue, 22 Apr 2025 04:47:53 -0400 Subject: [PATCH 16/17] formatted with prettier, other small changes. --- docs/design/README.md | 3 +- docs/design/lambdas.md | 193 +++++++++++++++++++++-------------------- 2 files changed, 99 insertions(+), 97 deletions(-) diff --git a/docs/design/README.md b/docs/design/README.md index 9a6cab57bdcd8..59a6f1048430f 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -3796,7 +3796,8 @@ the critical underpinnings of such abstractions. > **TODO:** References need to be evolved. Needs a detailed design and a high > level summary provided inline. -> References: [Lambdas](lambdas.md), [Proposal #3848: Lambdas](https://github.com/carbon-language/carbon-lang/pull/3848) +> References: [Lambdas](lambdas.md), +> [Proposal #3848: Lambdas](https://github.com/carbon-language/carbon-lang/pull/3848) #### Co-routines diff --git a/docs/design/lambdas.md b/docs/design/lambdas.md index 7f69ba5279ce8..a0ac26992dc12 100644 --- a/docs/design/lambdas.md +++ b/docs/design/lambdas.md @@ -12,29 +12,30 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Syntax Overview](#syntax-overview) - [Return type](#return-type) - - [Return expression](#return-expression) - - [Explicit return type](#explicit-return-type) - - [No return](#no-return) + - [Return expression](#return-expression) + - [Explicit return type](#explicit-return-type) + - [No return](#no-return) - [Implicit parameters in square brackets](#implicit-parameters-in-square-brackets) - [Parameters](#parameters) - - [Syntax Defined](#syntax-defined) -- [Positional Parameters](#positional-parameters) - - [Positional Parameter Restrictions](#positional-parameter-restrictions) -- [Lambda Captures](#lambda-captures) - - [Capture Modes](#capture-modes) - - [Default Capture Mode](#default-capture-mode) -- [Function Fields](#function-fields) -- [Copy Semantics](#copy-semantics) -- [Self and Recursion](#self-and-recursion) + - [Syntax defined](#syntax-defined) +- [Positional parameters](#positional-parameters) + - [Positional parameter restrictions](#positional-parameter-restrictions) +- [Captures](#captures) + - [Capture modes](#capture-modes) + - [Default capture mode](#default-capture-mode) +- [Function fields in lambdas](#function-fields-in-lambdas) +- [Copy semantics](#copy-semantics) +- [Self and recursion](#self-and-recursion) +- [Alternatives considered](#alternatives-considered) ## Syntax Overview -One goal of Carbon's lambda syntax is to have continuity between lambdas and function declarations. -Below are some example declarations: +One goal of Carbon's lambda syntax is to have continuity between lambdas and +function declarations. Below are some example declarations: -Implicit return types +Implicit return types: ```carbon // In a variable: @@ -43,12 +44,12 @@ let lambda: auto = fn => T.Make(); // const auto lambda = [] { return T::Make(); }; // As an argument to a function call: -Foo(10, E20, fn => T.Make()); +Foo(10, 20, fn => T.Make()); // Equivalent in C++23: // Foo(10, 20, [] { return T::Make(); }); ``` -Explicit return types +Explicit return types: ```carbon // In a variable: @@ -65,16 +66,16 @@ PushBack(my_list, fn -> T { return T.Make() }); ### Return type There are three options for how a lambda expresses its return type, parallel to -[how function declarations express returns](functions.md#return-clause): using -a return expression, using an explicit return type, or having no return. +[how function declarations express returns](functions.md#return-clause): using a +return expression, using an explicit return type, or having no return. #### Return expression A return expression is introduced with a double arrow (`=>`) followed by an -expression describing the function's return value. In this case, the return type is -determined by the type of the expression, as if the return type was `auto`. +expression describing the function's return value. In this case, the return type +is determined by the type of the expression, as if the return type was `auto`. -```carbon +```carbon // In a variable: let lambda: auto = fn => T.Make(); // Equivalent in C++23: @@ -88,9 +89,9 @@ Foo(fn => T.Make()); #### Explicit return type -An explicit return type is introduced with a single arrow (`->`), followed by the -return type, and finally the body of the lambda with a sequence of statements -enclosed in curly braces (`{`...`}`). +An explicit return type is introduced with a single arrow (`->`), followed by +the return type, and finally the body of the lambda with a sequence of +statements enclosed in curly braces (`{`...`}`). ```carbon // In a variable: @@ -99,7 +100,7 @@ let lambda: auto = fn -> T { return T.Make(); }; // const auto lambda = [] -> T { return T::Make(); }; // As an argument to a function call: -Foo(fn -> T { return T.Make(); }) +Foo(fn -> T { return T.Make(); }); // Equivalent in C++23: // Foo([] -> T { return T::Make(); }); ``` @@ -123,7 +124,7 @@ Foo(fn { Print(T.Make()); }); ### Implicit parameters in square brackets -Lambdas support [captures](#function-captures), [fields](#function-fields) and +Lambdas support [captures](#captures), [fields](#function-fields-in-lambdas) and deduced parameters in the square brackets. ```carbon @@ -133,7 +134,7 @@ fn Foo(x: i32) { // Equivalent in C++23: // const auto lambda = [x, y = int32_t{0}] mutable -> void { Print(++x, ++y); }; - // As an argument to a function calll: + // As an argument to a function call: Foo(fn [var x, var y: i32 = 0] { Print(++x, ++y); }); // Equivalent in C++23: // Foo([x, y = int32_t{0}] mutable -> void { Print(++x, ++y); }); @@ -142,9 +143,9 @@ fn Foo(x: i32) { ### Parameters -Lambdas also support so-called ["positional parameters"](#positional-parameters) -that are defined at their point of use using a dollar sign and a non-negative integer. -They are implicitly of type `auto`. +Lambdas also support so-called ["positional parameters"](#positional-parameters) +that are defined at their point of use using a dollar sign and a non-negative +integer. They are implicitly of type `auto`. ```carbon fn Foo() { @@ -182,18 +183,18 @@ fn Foo() { } ``` -### Syntax Defined +### Syntax defined -Lambda expressions have one of the following syntactic -forms (where items in square brackets are optional and independent): +Lambda expressions have one of the following syntactic forms (where items in +square brackets are optional and independent): `fn`\[_implicit-parameters_\] \[_tuple-pattern_\] `=>` _expression_ -`fn` \[_implicit-parameters_\] \[_tuple-pattern_\] \[`->` -_return-type_\] `{` _statements_ `}` +`fn` \[_implicit-parameters_\] \[_tuple-pattern_\] \[`->` _return-type_\] `{` +_statements_ `}` -The first form is a shorthand for the second: "`=>` _expression_" is -equivalent to "`-> auto { return` _expression_ `; }`". +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 @@ -214,18 +215,18 @@ 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. -| Syntactic Position | Syntax Allowed in Given Position (optional, unless otherwise stated) | -| :----------------: | :----------------------------------------------------------------------------------------------------------------: | -| A1 | Required Returned Expression ([positional parameters](#positional-parameters) allowed) | -| A2 | Required Returned Expression ([positional parameters](#positional-parameters) disallowed) | -| B | [Default Capture Mode](#default-capture-mode) | -| C | Explicit [Captures](#function-captures), [Function Fields](#function-fields) and Deduced Parameters (in any order) | -| D | Explicit Parameters | -| E1 | Body of Statements (no return value) ([positional parameters](#positional-parameters) allowed) | -| E2 | Body of Statements (with return value) ([positional parameters](#positional-parameters) allowed) | -| E3 | Body of Statements (no return value) ([positional parameters](#positional-parameters) disallowed) | -| E4 | Body of Statements (with return value) ([positional parameters](#positional-parameters) disallowed) | -| F | Required Return Type | +| Syntactic Position | Syntax Allowed in Given Position (optional, unless otherwise stated) | +| :----------------: | :------------------------------------------------------------------------------------------------------------------: | +| A1 | Required Returned Expression ([positional parameters](#positional-parameters) allowed) | +| A2 | Required Returned Expression ([positional parameters](#positional-parameters) disallowed) | +| B | [Default capture mode](#default-capture-mode) | +| C | Explicit [Captures](#captures), [Function fields](#function-fields-in-lambdas) and Deduced Parameters (in any order) | +| D | Explicit Parameters | +| E1 | Body of Statements (no return value) ([positional parameters](#positional-parameters) allowed) | +| E2 | Body of Statements (with return value) ([positional parameters](#positional-parameters) allowed) | +| E3 | Body of Statements (no return value) ([positional parameters](#positional-parameters) disallowed) | +| E4 | Body of Statements (with return value) ([positional parameters](#positional-parameters) disallowed) | +| F | Required Return Type | ```carbon // Lambdas (all the following are in an expression context and are @@ -256,22 +257,23 @@ fn [B, C](D) { E3; } fn [B, C](D) -> F { E4; } ``` -## Positional Parameters +## Positional parameters -Positional parameters, denoted by a dollar sign followed by a non-negative -integer (e.g., $3), are auto-typed parameters defined within the lambda's body. +Positional parameters, denoted by a dollar sign followed by a non-negative +integer (for example, $3), are auto-typed parameters defined within the lambda's +body. ```carbon -let lambda: auto = fn => { return $0 }; +let lambda: auto = fn => $0 ``` -They can be used in any lambda declaration that lacks an explicit -parameter list (parentheses). They are variadic by design, meaning an unbounded -number of arguments can be passed to any function that lacks an explicit -parameter list. Only the parameters that are named in the body will be read -from, meaning the highest named parameter denotes the minimum number of -arguments required by the function. The lambda body is free to omit -lower-numbered parameters (ex: `fn { Print($10); }`). +They can be used in any lambda declaration that lacks an explicit parameter list +(parentheses). They are variadic by design, meaning an unbounded number of +arguments can be passed to any function that lacks an explicit parameter list. +Only the parameters that are named in the body will be read from, meaning the +highest named parameter denotes the minimum number of arguments required by the +function. The lambda body is free to omit lower-numbered parameters (ex: +`fn { Print($10); }`). This syntax was inpsired by Swift's [Shorthand Argument Names](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/closures/#Shorthand-Argument-Names). @@ -282,16 +284,16 @@ Sort(my_list, fn => $0.val < $1.val); // In Swift: { $0.val < $1.val } ``` -### Positional Parameter Restrictions +### Positional parameter restrictions -Lambdas with positional parameters have the restriction that they -can only be used in a context where there is exactly one enclosing -function or lambda that has no explicit parameter list. For example: +Lambdas with positional parameters have the restriction that they can only be +used in a context where there is exactly one enclosing function or lambda that +has no explicit parameter list. For example: ```carbon fn Foo1 { // ❌ Invalid: Foo1 is already using positional parameters - let lambda: auto = fn => ( $0 < $1 ) + let lambda: auto = fn => $0 < $1 } fn Foo2 { @@ -323,11 +325,11 @@ fn Foo5() { ## Captures -Captures in Carbon mirror the non-init captures of C++. A - capture declaration consists of a capture mode (for `var` captures) -followed by the name of a binding from the enclosing scope, and makes that -identifier available in the inner function body. The lifetime of a capture is -the lifetime of the function in which it exists. For example... +Captures in Carbon mirror the non-init captures of C++. A capture declaration +consists of a capture mode (for `var` captures) followed by the name of a +binding from the enclosing scope, and makes that identifier available in the +inner function body. The lifetime of a capture is the lifetime of the function +in which it exists. For example... ```carbon fn Foo() { @@ -346,18 +348,17 @@ fn Foo() { } ``` -### Capture Modes +### Capture modes -Lambdas can capture variables from their surrounding scope using `let` or `var`, +Lambdas can capture variables from their surrounding scope using `let` or `var`, just like regular bindings. - Capture modes can be used as [default capture mode specifiers](#default-capture-mode) or for explicit captures as shown in the example code below. ```carbon -fn Example { +fn Example() { var a: i32 = 0; var b: i32 = 0; @@ -389,14 +390,14 @@ 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. -### Default Capture Mode +### Default capture mode -By default, there is no capturing in lambdas. The lack of any -square brackets is the same as an empty pair of square brackets. Users can opt -into capturing behavior. This is done either by way of individual explicit -captures, or more succinctly by way of a default capture mode. The default -capture mode roughly mirrors the syntax `[=]` and `[&]` capture modes from C++ -by being the first thing to appear in the square brackets. +By default, there is no capturing in lambdas. The lack of any square brackets is +the same as an empty pair of square brackets. Users can opt into capturing +behavior. This is done either by way of individual explicit captures, or more +succinctly by way of a default capture mode. The default capture mode roughly +mirrors the syntax `[=]` and `[&]` capture modes from C++ by being the first +thing to appear in the square brackets. ```carbon fn Foo1() { @@ -420,11 +421,11 @@ fn Foo2() { } ``` -## Function Fields in Lambdas +## Function fields in lambdas Function fields in lambdas 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 lambda +initializer. It matches the pattern with the initializer when the lambda 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. @@ -440,22 +441,22 @@ fn Foo() { } ``` -## Copy Semantics +## Copy semantics -To mirror the behavior of C++, lambdas will be as copyable as their contained -function fields and function captures. 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 captures. +To mirror the behavior of C++, lambdas will be as copyable as their contained +function fields and function captures. 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 captures. The other case is by-value function fields. Since C++ const references, when made into fields of a class, prevent the class from being copied assigned, so too should by-value function fields prevent the function in which it is contained from being copied assigned. -## Self and Recursion +## Self and recursion -To mirror C++'s use of capturing `this`, `self` should always come -from the outer scope as a capture. `self: Self` is never permitted on lambdas. +To mirror C++'s use of capturing `this`, `self` should always come from the +outer scope as a capture. `self: Self` is never permitted on lambdas. ```carbon // ❌ Not allowed @@ -474,7 +475,7 @@ function fields with a `self` parameter. ## Alternatives considered -- [Terse vs Elaborated](/proposals/p3848.md#alternative-considered-terse-vs-elaborated) -- [Sigil](/proposals/p3848.md#alternative-considered-sigil) -- [Additional Positional Parameter Restriction](/proposals/p3848.md#alternative-considered-additional-positional-parameter-restriction) -- [Recursive Self](/proposals/p3848.md#alternative-considered-recursive-self) +- [Terse vs Elaborated](/proposals/p3848.md#alternative-considered-terse-vs-elaborated) +- [Sigil](/proposals/p3848.md#alternative-considered-sigil) +- [Additional Positional Parameter Restriction](/proposals/p3848.md#alternative-considered-additional-positional-parameter-restriction) +- [Recursive Self](/proposals/p3848.md#alternative-considered-recursive-self) From f085dec36fac9fefaf9ba08f43bea2ea1b7efa00 Mon Sep 17 00:00:00 2001 From: Ivan Duran Date: Fri, 25 Apr 2025 18:42:22 -0400 Subject: [PATCH 17/17] Apply suggestions from code review Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com> --- docs/design/lambdas.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/docs/design/lambdas.md b/docs/design/lambdas.md index a0ac26992dc12..efaf51cbc4dcb 100644 --- a/docs/design/lambdas.md +++ b/docs/design/lambdas.md @@ -298,13 +298,15 @@ fn Foo1 { fn Foo2 { my_list.Sort( - fn => $0 < $1 // ❌ Invalid: Foo2 is already using positional parameters + // ❌ Invalid: Foo2 is already using positional parameters + fn => $0 < $1 ); } fn Foo3() { my_list.Sort( - fn => $0 < $1 // ✅ Valid: Foo3 has explicit parameters + // ✅ Valid: Foo3 has explicit parameters + fn => $0 < $1 ); } @@ -363,8 +365,10 @@ fn Example() { var b: i32 = 0; let lambda: auto = fn [a, var b] { - a += 1; // ❌ Invalid: by-value captures are immutable (default `let`) - b += 1; // ✅ Valid: `b` is a mutable copy (captured with `var`) + // ❌ Invalid: by-value captures are immutable (default `let`) + a += 1; + // ✅ Valid: `b` is a mutable copy (captured with `var`) + b += 1; }; lambda(); @@ -403,8 +407,9 @@ thing to appear in the square brackets. fn Foo1() { let handle: Handle = Handle.Get(); fn MyThread[var]() { - handle.Process(); // `handle` is captured by-object due to the default capture - // mode specifier of `var` + // `handle` is captured by-object due to the default capture + // mode specifier of `var` + handle.Process(); } var thread: Thread = Thread.Make(MyThread); thread.Join(); @@ -413,8 +418,9 @@ fn Foo1() { fn Foo2() { let handle: Handle = Handle.Get(); fn MyThread[let]() { - handle.Process(); // `handle` is captured by-value due to the default capture - // mode specifier of `let` + // `handle` is captured by-value due to the default capture + // mode specifier of `let` + handle.Process(); } var thread: Thread = Thread.Make(MyThread); thread.Join();