diff --git a/docs/design/README.md b/docs/design/README.md index 59f87c28ba69..96974e72b559 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -3821,7 +3821,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: [Functions](functions.md), > [Proposal #3848: Lambdas](https://github.com/carbon-language/carbon-lang/pull/3848) #### Co-routines diff --git a/docs/design/functions.md b/docs/design/functions.md index eaf984eba795..f3a4a16365ca 100644 --- a/docs/design/functions.md +++ b/docs/design/functions.md @@ -10,14 +10,25 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ## Table of contents -- [Overview](#overview) -- [Function definitions](#function-definitions) +- [Syntax](#syntax) + - [Implicit parameters](#implicit-parameters) + - [Named and positional parameters](#named-and-positional-parameters) + - [Body](#body) +- [Function and lambda definitions](#function-and-lambda-definitions) - [Function signatures](#function-signatures) - - [Captures and function fields](#captures-and-function-fields) - - [Positional Parameters](#positional-parameters) - [Return specification](#return-specification) - [Unused parameters](#unused-parameters) - [`return` statements](#return-statements) +- [Positional parameters](#positional-parameters) +- [Captures](#captures) + - [Capture restrictions on named functions](#capture-restrictions-on-named-functions) + - [Capture modes](#capture-modes) + - [Default capture mode](#default-capture-mode) + - [Function fields](#function-fields) +- [Copy semantics](#copy-semantics) +- [Lambdas](#lambdas) + - [Lambdas may not take `self` as a parameter](#lambdas-may-not-take-self-as-a-parameter) + - [Lambda and function syntax comparison](#lambda-and-function-syntax-comparison) - [Forward declarations](#forward-declarations) - [Redeclaration matching](#redeclaration-matching) - [Function types and values](#function-types-and-values) @@ -32,36 +43,73 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -## Overview +## Syntax -Functions are the core building block for applications. A function definition or -declaration has one of the following syntactic forms (where items in square -brackets are optional and independent): +Functions are the core building block for applications. Carbon supports both +named function declarations and anonymous function expressions called _lambdas_. + +A named function definition or declaration has 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-form_] `{` _statements_ `}` -- `fn` _name_ [_implicit-parameters_] [_tuple-pattern_] [`->` _return-form_] `;` +- `fn` _name_ [_implicit-parameters_] _tuple-pattern_ [`->` _return-form_] `;` -The first form is a shorthand: `=> expression ;` is equivalent to -`-> auto { return expression; }`. When a body is present (the first and second -forms), it is a function definition. The body introduces nested scopes which may -contain local variable declarations. A function with only a signature and no -body (the third form) is a forward declaration. +A lambda expression has one of the following syntactic forms: -The syntax for parameters and returns is the same for functions and -[lambdas](lambdas.md#syntax-overview): +- `fn` [_implicit-parameters_] [_tuple-pattern_] `=>` _expression_ +- `fn` [_implicit-parameters_] [_tuple-pattern_] [`->` _return-form_] `{` _statements_ + `}` -- _implicit-parameters_: square brackets `[`...`]` enclosing default capture - modes, explicit captures, function fields, or deduced parameters, see - [lambdas](lambdas.md#implicit-parameters-in-square-brackets). -- _tuple-pattern_: parentheses `(`...`)` enclosing a list of explicit - parameter patterns, see - [pattern matching](pattern_matching.md#pattern-syntax-and-semantics). +Named function definitions are distinguished from lambdas by the presence of a +name after the `fn` keyword. 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. -## Function definitions +The first form in both cases is a shorthand: `=> expression` is equivalent to +`-> auto { return expression; }` (with a trailing semicolon for named +functions). -A basic function definition may look like: +The syntax for parameters, captures, and returns is the same for functions and +lambdas. + +### Implicit parameters + +The optional _implicit-parameters_ part of a function declaration consists of +square brackets `[]` enclosing zero or more comma-separated items. Each item is +either a default capture mode, an explicit capture, a function field, or a +deduced parameter. The default capture mode, if present, must come first; the +other items can appear in any order. If _implicit-parameters_ is omitted, it is +equivalent to `[]`. + +See [captures](#captures) and +[deduced parameters](generics/overview.md#deduced-parameters) for details. + +### Named and positional parameters + +The presence of _tuple-pattern_ determines whether the function body uses named +or positional parameters. + +> _tuple-pattern_: parentheses `(`...`)` enclosing a list of explicit parameter +> patterns + +See the +[pattern matching design](pattern_matching.md#pattern-syntax-and-semantics) for +details about named parameters, and +[positional parameters](#positional-parameters) for details about positional +parameters. + +### Body + +When a body is present (in `{`...`}` or after `=>`), it is a function or lambda +definition. The body introduces nested scopes which may contain local variable +declarations. A named function with only a signature and no body is a forward +declaration. + +## Function and lambda definitions + +A basic named function definition may look like: ```carbon fn Add(a: i64, b: i64) -> i64 { @@ -93,31 +141,10 @@ auto Add(std::int64_t a, std::int64_t b) -> std::int64_t { ### Function signatures -#### Captures and function fields - -Like lambdas, named function definitions support [captures](lambdas.md#captures) -and [function fields](lambdas.md#function-fields), with these restrictions: - -- They can only be used on functions where the definition is attached to the - declaration (so they cannot be forward declared). -- Captures and function fields are only supported on local function - definitions immediately defined inside the body of another function. They - are not supported on member functions of classes/interfaces. - -#### Positional Parameters - -Like lambdas, named function definitions support -[positional parameters](lambdas.md#positional-parameters), which are used when -the explicit parameter list is omitted. Like -[captures and function fields](#captures-and-function-fields), they may only be -used with function definitions and not forward declarations. In addition, -positional parameters can only be used in a context where there is exactly one -enclosing function or lambda that has no explicit parameter list. - #### Return specification -The return type of a function can be specified using a return clause (`->`), or -it can be deduced using a signature return expression (`=>`). +The return type of a function or lambda can be specified using a return clause +(`->`), or it can be deduced using a signature return expression (`=>`). - `->` followed by a return form: - Most commonly, this will be an _expression_ that directly states the @@ -147,6 +174,8 @@ it can be deduced using a signature return expression (`=>`). - For example, `fn Sleep(seconds: i64);` is similar to `fn Sleep(seconds: i64) -> ();`. - `()` is similar to a `void` return type in C++. + - If `->` is omitted, `return` statements in the function body must not be + followed by an expression. - `=>` followed by an _expression_ defines a shorthand for a function body that returns the expression. The return type is deduced as if `-> auto` were used. @@ -207,6 +236,341 @@ statement must have an expression that is convertible to the return type, and a > **TODO:** Update this section to cover the requirements on the form of the > expression. +## Positional parameters + +Positional parameters, denoted by a dollar sign followed by a non-negative +integer (for example, `$3`), are auto-typed parameters defined within the +function or lambda body when the explicit parameter list (parentheses) is +omitted. + +```carbon +let lambda: auto = fn => $0 +``` + +They are variadic by design, meaning an unbounded number of arguments can be +passed to any function or lambda 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 body is free to omit lower-numbered parameters (for example, +`fn { Print($10); }`). + +This syntax was inspired by Swift's +[Shorthand Argument Names](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/closures/#Shorthand-Argument-Names). + +```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 } +``` + +When positional parameters are used in a nested function definition, exactly one +of the enclosing functions must omit the explicit parameter list, and they are +interpreted as parameters of that function: + +```carbon +fn F { + fn G[let]() -> auto { + // `$0` is a parameter of `F` that is captured by `G`, + // not a parameter of `G`. + return $0; + } +} +``` + +This means that a single function or lambda cannot have both named and +positional parameters: + +```carbon +fn Foo(x: i32) -> i32 { + // ❌ Invalid since `Foo` is already using named parameters. + return $0; +} +``` + +Positional parameters can only be used with function definitions, not forward +declarations. + +## 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. These captures are specified in square brackets `[`...`]` +as part of the implicit parameter list. The lifetime of a capture is the +lifetime of the function in which it exists. + +For example: + +```carbon +fn InLambda() { + let handle: Handle = Handle.Get(); + var thread: Thread = Thread.Make(fn [var handle] { handle.Process(); }); + thread.Join(); +} +``` + +```carbon +fn InNamedFunction() { + let handle: Handle = Handle.Get(); + fn MyThread[handle]() { handle.Process(); } + var thread: Thread = Thread.Make(MyThread); + thread.Join(); +} +``` + +### Capture restrictions on named functions + +While lambdas can use captures freely, named function definitions support +captures (and function fields) with these restrictions: + +- They can only be used on functions where the definition is attached to the + declaration (so they cannot be forward declared). +- Captures and function fields are only supported on local function + definitions immediately defined inside the body of another function. + +### Capture modes + +Lambdas and local functions 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() { + var a: i32 = 0; + var b: i32 = 0; + + let lambda: auto = fn [a, var b] { + // ❌ Invalid: by-value captures are immutable (default `let`) + a += 1; + // ✅ Valid: `b` is a mutable copy (captured with `var`) + b += 1; + }; + + 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 + +By default, there is no capturing in lambdas and 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` is captured by-object due to the default capture + // mode specifier of `var` + handle.Process(); + } + var thread: Thread = Thread.Make(MyThread); + thread.Join(); +} + +fn Foo2() { + let handle: Handle = Handle.Get(); + fn MyThread[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(); +} +``` + +### Function fields + +Function fields mirror the behavior of init captures in C++. Function fields are +defined in the implicit parameter list, and are allowed only where captures are +allowed. 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. + +```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 + +To mirror the behavior of C++, lambdas and functions with captures or function +fields 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 copy assigned, so too +should by-value function fields prevent the function in which it is contained +from being copy assigned. + +## Lambdas + +One goal of Carbon's lambda syntax is to have continuity between lambdas and +named functions. Below are some example declarations: + +Implicit return types: + +```carbon +// In a variable: +let lambda: auto = fn => T.Make(); +// Equivalent in C++23: +// const auto lambda = [] { return T::Make(); }; + +// As an argument to a function call: +Foo(10, 20, 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(); }; + +// 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(); }); +``` + +### Lambdas may not take `self` as a parameter + +To mirror C++'s use of capturing `this`, `self` should always come from the +outer scope as a capture. `self` is never permitted in the explicit parameter +list of a lambda. + +```carbon +// ❌ Not allowed, lambdas can't be methods. +let lambda: auto = fn (self) { self.F(); }; + +// ✅ Captures `self` from outer scope +let lambda: auto = fn [self] { self.F(); }; +``` + +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 `ref 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. + +### Lambda and function syntax comparison + +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](#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 | + +Lambdas (all the following are in an expression context and are themselves +expressions): + +```carbon +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; } +``` + +Function Declarations (all the following are allowed as statements in a function +body or as declarations in other scopes): + +```carbon +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; } +``` + ## Forward declarations Functions may be declared separate from the definition by providing only a @@ -521,6 +885,10 @@ Other designs build upon basic function syntax to add advanced features: - [Allow separate declaration and definition](/proposals/p000826-function-return-type-inference.md#allow-separate-declaration-and-definition) - [Signature-based function types](/proposals/p002875-functions-function-types-and-function-calls.md#signature-based-function-types) - [Make direct and indirect calls behave uniformly](/proposals/p002875-functions-function-types-and-function-calls.md#make-direct-and-indirect-calls-behave-uniformly) +- [Terse vs Elaborated lambdas](/proposals/p003848-lambdas.md#alternative-considered-terse-vs-elaborated) +- [Sigil for lambdas](/proposals/p003848-lambdas.md#alternative-considered-sigil) +- [Additional Positional Parameter Restriction](/proposals/p003848-lambdas.md#alternative-considered-additional-positional-parameter-restriction) +- [Recursive Self in lambdas](/proposals/p003848-lambdas.md#alternative-considered-recursive-self) ## References diff --git a/docs/design/lambdas.md b/docs/design/lambdas.md deleted file mode 100644 index 3977aebbb35c..000000000000 --- a/docs/design/lambdas.md +++ /dev/null @@ -1,494 +0,0 @@ -# Lambdas - - - - - -## 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) -- [Positional parameters](#positional-parameters) - - [Positional parameter restrictions](#positional-parameter-restrictions) -- [Captures](#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) -- [Alternatives considered](#alternatives-considered) -- [References](#references) - - - -## Syntax Overview - -One goal of Carbon's lambda syntax is to have continuity between lambdas and -function declarations. Below are some example declarations: - -Implicit return types: - -```carbon -// In a variable: -let lambda: auto = fn => T.Make(); -// Equivalent in C++23: -// const auto lambda = [] { return T::Make(); }; - -// As an argument to a function call: -Foo(10, 20, 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(); }; - -// 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(); }); -``` - -### Return type - -There are three options for how a lambda expresses its return type, parallel to -[how function declarations express returns](functions.md#return-specification): -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`. - -```carbon -// In a variable: -let lambda: auto = fn => T.Make(); -// Equivalent in C++23: -// const auto lambda = [] { return T::Make(); }; - -// As an argument to a function call: -Foo(fn => T.Make()); -// Equivalent in C++23: -// Foo([] { return 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 (`{`...`}`). - -```carbon -// In a variable: -let lambda: auto = fn -> T { return T.Make(); }; -// Equivalent in C++23: -// const auto lambda = [] -> T { return T::Make(); }; - -// As an argument to a function call: -Foo(fn -> T { return T.Make(); }); -// Equivalent in C++23: -// Foo([] -> T { return T::Make(); }); -``` - -#### No return - -Lambdas that don't return anything end with a body of statements in curly braces -(`{`...`}`). - -```carbon -// In a variable: -let lambda: auto = fn { Print(T.Make()); }; -// Equivalent in C++23: -// const auto lambda = [] -> void { Print(T::Make()); }; - -// As an argument to a function call: -Foo(fn { Print(T.Make()); }); -// Equivalent in C++23: -// Foo([] -> void { Print(T::Make()); }); -``` - -### Implicit parameters in square brackets - -Lambdas support [captures](#captures), [fields](#function-fields) and deduced -parameters in the square brackets. - -```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); }; - - // 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); }); -} -``` - -### 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() { - 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) }; -} -``` - -Of course, lambdas can also have named parameters, but a single lambda 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); }; - - // As an argument to a function call: - Foo(fn (v: auto) { Print(v); }); - // Equivalent in C++23: - // Foo([](v: auto) { 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); }; -} -``` - -### Syntax defined - -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_ `}` - -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 `[]`. - -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. - -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](#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 | - -```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; } -``` - -## Positional parameters - -Positional parameters, denoted by a dollar sign followed by a non-negative -integer (for example, $3), are auto-typed parameters defined within the function -or lambda body. - -```carbon -let lambda: auto = fn => $0 -``` - -They can be used in any lambda or function definition 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 body is free to omit lower-numbered -parameters (for example, `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). - -```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 } -``` - -### 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: - -```carbon -fn Foo1 { - // ❌ Invalid: Foo1 is already using positional parameters - let lambda: auto = fn => $0 < $1 -} - -fn Foo2 { - my_list.Sort( - // ❌ Invalid: Foo2 is already using positional parameters - fn => $0 < $1 - ); -} - -fn Foo3() { - my_list.Sort( - // ✅ Valid: Foo3 has explicit parameters - fn => $0 < $1 - ); -} - -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); - }; -} -``` - -## 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... - -```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 - -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() { - var a: i32 = 0; - var b: i32 = 0; - -let lambda: auto = fn [a, var b] { - // ❌ Invalid: by-value captures are immutable (default `let`) - a += 1; - // ✅ Valid: `b` is a mutable copy (captured with `var`) - b += 1; -}; - - 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 - -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() { - let handle: Handle = Handle.Get(); - fn MyThread[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(); -} - -fn Foo2() { - let handle: Handle = Handle.Get(); - fn MyThread[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(); -} -``` - -## Function fields - -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. - -```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 - -To mirror the behavior of C++, lambdas and functions with captures or function -fields 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 - -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, lambdas can't be methods. -let lambda: auto = fn (self) { self.F(); }; - -// ✅ Captures `self` from outer scope -let lambda: auto = fn [self] { self.F(); }; -``` - -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 `ref 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/p003848-lambdas.md#alternative-considered-terse-vs-elaborated) -- [Sigil](/proposals/p003848-lambdas.md#alternative-considered-sigil) -- [Additional Positional Parameter Restriction](/proposals/p003848-lambdas.md#alternative-considered-additional-positional-parameter-restriction) -- [Recursive Self](/proposals/p003848-lambdas.md#alternative-considered-recursive-self) - -## References - -- Proposal - [#3848: Lambdas](https://github.com/carbon-language/carbon-lang/pull/3848) diff --git a/proposals/p005434-ref-parameters-arguments-returns-and-val-returns.md b/proposals/p005434-ref-parameters-arguments-returns-and-val-returns.md index aa2ce1f7b5c0..5d78a4681619 100644 --- a/proposals/p005434-ref-parameters-arguments-returns-and-val-returns.md +++ b/proposals/p005434-ref-parameters-arguments-returns-and-val-returns.md @@ -89,7 +89,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception Reference bindings have come up multiple times: - as a better alternative to `addr self: Self*`; -- for use in [lambda captures](/docs/design/lambdas.md); +- for use in [lambda captures](/docs/design/functions.md#captures); - to model different kinds of C++ references for interop and migration; - to support nested bindings within a destructured `var`, see [issue #5250](https://github.com/carbon-language/carbon-lang/issues/5250)