# 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 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: 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-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`. ```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-in-lambdas) 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-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 // 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 lambda's body. ```carbon 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); }`). 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 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 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 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 let lambda: auto = fn [self: 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/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)