mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 13:50:10 +01:00
Merge functions.md and lambdas.md design documents (#7425)
Implements suggestion from https://github.com/carbon-language/carbon-lang/pull/7355#discussion_r3416055969 . Assisted-by: Gemini via Antigravity --------- Co-authored-by: Josh L <josh11b@users.noreply.github.com> Co-authored-by: Geoff Romer <gromer@google.com>
This commit is contained in:
co-authored by
Josh L
Geoff Romer
parent
e4b0903d2e
commit
73744544fc
@@ -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
|
||||
|
||||
+415
-47
@@ -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
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
## 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
|
||||
|
||||
|
||||
@@ -1,494 +0,0 @@
|
||||
# Lambdas
|
||||
|
||||
<!--
|
||||
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
Exceptions. See /LICENSE for license information.
|
||||
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
-->
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
## 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)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
## 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)
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user