Function Objects
A lambda expression is a notation that generates a function object, similar in kind to writing Less_than<int>{x}.
Try it: Functions as Values: Lambda
That a lambda (like def) evaluates to a function object that a name can refer to and that can be passed to other functions — apply(f, x), twice(f, x), map(f, xs), sorted(xs, key=f) — and that each call is traced by binding the argument, substituting it into the single expression and evaluating in operator order.
How it works
- lambda x: expression evaluates to a function object (named <lambda>); nothing inside it is computed yet. f = ... gives it a name, exactly as def f(x): return expression would.
- Writing f without parentheses passes the function object itself; inside apply or twice the parameter func refers to the same object.
- A call binds the argument to the parameter x, substitutes it into the expression, and evaluates by operator precedence (* and % before + and -, parentheses first).
- The value of the expression is the call's return value; there is no other body.
- map(f, xs) calls f once per item and keeps the results in order; sorted(xs, key=f) calls f once per item to get keys, orders by the keys (ties keep their order) and returns the items.
Default run (9 steps): The program is ready: a lambda written inline, then f(x). … print shows 7.
Simplified: Four one-parameter lambdas with editable integer constants, small int arguments (at most 6 items). Each substituted expression the lab shows was evaluated by CPython 3.12 and has the same value as the real call; printed results and ZeroDivisionError messages match CPython.
Loading the simulation…
A Small Function Written Where It Is Needed
Many useful functions are small and short-lived. You write one to hand to an algorithm, to filter a collection, or to sort some items, and then you never need it again. Writing a full named function for each of those jobs is heavy. A lambda expression lets you write the function right where it is needed.
The harder question is what such a small function is allowed to see. Its body may refer to names that were declared in the surrounding code. The capture list is the part of the lambda that answers how those outside names are reached. Keep one question in mind as you read: when the lambda's body mentions a local name from the place where the lambda was written, does the function work with that name itself, or with a separate value it has been given?
What a Lambda Expression Produces
A lambda expression is a notation that generates a function object. The source describes the object it creates as the same kind of thing you would get from writing Less_than<int>{x}, which is a function object constructed with x as its stored value.
So a lambda is not a special kind of call. It is a compact way of producing an object that can be called with an int and that returns the result of a comparison. Consider the notation [&](int a){ return a<x; }. The name less_than_x can hold the function object that this lambda generated. Calling it with 5 runs the body a < x using the parameter a. The part between the first pair of square brackets is the capture list, and it is what lets the body reach x, which is declared outside the lambda.
Read the diagram from top to bottom. The lambda expression is the notation you write. The compiler-generated function object is what that notation produces, and it is what the name less_than_x ends up holding. Each name the capture list brings in becomes something the object can use, which is why the capture list decides the behavior of the object as a whole.
Tracing a Lambda Call Step by Step
- x is a local variable declared in the surrounding code
- the lambda captures x by reference through the bare ampersand
- the object is stored in
less_than_xand can be called like a function - 5 is passed as the parameter a, so the body evaluates 5 < 10
Following one call to less_than_x
Given the code above, what is the value of result, and which value of x does the body use?
- 1Create the object
The lambda expression generates a function object and the name
less_than_xholds it. - 2Call the object
less_than_x(5)starts running the body with the parameter a set to 5. - 3Find the name x
The body refers to x. The capture list [&] tells us x is accessed by reference, so the body works with the original local x.
- 4Evaluate the comparison
The body computes a < x, which is 5 < 10.
result is true, because 5 is less than 10.
What do you think happens?
If the code runs with x set to 10 and the lambda captured x by reference, what does less_than_x(5) return?
Reveal answer
true
The body evaluates 5 < 10, which is true. The result depends on the value of x that the body sees at the time of the call.
Reading the Capture List
A capture list always sits first, inside square brackets, before the parameter list. The rule it encodes is simple once you see the pattern. A bare ampersand or equals sign applies to every local name the body uses. An ampersand or equals sign followed by a name applies only to that one name. The ampersand means the body works through a reference to the original. The equals sign means the generated object receives a copy of the value. Empty brackets mean the body is given no local names at all.
- [&] : every local name the body uses is accessed by reference
- [=] : every local name the body uses is copied into the generated object
- [&x] : only x is accessed by reference
- [=x] : only x is copied into the generated object
- [ ] : the body is given no local names from outside
Reference Captures Versus Value Captures
Suppose a program has a local variable x and wants two function objects that compare an incoming int against it. One lambda should refer to x directly, and the other should carry its own copy. The two lambdas look nearly identical, so the capture list is the only place where their behavior is decided. A reference capture always lets the body see the current state of the original name. A value capture lets the object keep its own copy, separate from the original.
- both lambdas have the same body and the same parameter list
- only the capture list differs: [&] for
by_referenceand [=] forby_value - x is changed to 3 after both objects are created
by_valuewas given its own copy of x when it was created
What do you think happens?
After x is changed to 3, what are r1 and r2?
Reveal answer
r1 is false and r2 is true
by_reference reads the current x, which is 3, so 5 < 3 is false. by_value keeps its own copy made when it was created, which is 10, so 5 < 10 is true.
Choosing a Capture Style
The four styles answer different questions. Ask first whether the body needs any local names at all. If it does not, capture nothing. If it does, decide whether the body should see the original names and their current values, or work with copies the object keeps. Use a single named capture when only one local is needed and the rest should stay out of the body.
| Capture list | What it applies to | How the body reaches the name | When to use it |
|---|---|---|---|
| [ ] | No local names | The body cannot use outside locals | The body needs only its parameters |
| [&] | Every local name the body uses | By reference to the original | Many outside names are needed and current values matter |
| [=] | Every local name the body uses | By a copy stored in the object | Many outside names are needed and a snapshot is wanted |
| [&x] | Only the named local x | By reference to the original x | Exactly one outside name is needed and its current value matters |
| [=x] | Only the named local x | By a copy of x stored in the object | Exactly one outside name is needed and a snapshot of it is wanted |
The four capture choices in the source, with the single named capture forms included
Given int limit = 7; and a lambda that should report whether its parameter is greater than limit, which capture list would you write if limit must always reflect its current value, and which if the object should keep the value it had when created?
Hints
- Ask whether the body must see the original limit or a separate copy.
- A reference capture always reads the current state of the original name.
- A value capture keeps its own copy, made when the object was created.
Mistakes With Capture Lists
Common mistake Assuming a lambda with [=] shares the original local
What it looks likeint x = 10; auto f = [=](int a) { return a < x; }; x = 3; f(5) still compares against 10.Why it failsA value capture gives the generated object its own copy, separate from the original name.
Do this insteadUse [&] or [&x] when the body must see later changes to the original local.
Common mistake Expecting [ ] to make an outside name visible
What it looks likeint x = 10; auto f = [](int a) { return a < x; };Why it failsEmpty brackets mean the body is given no local names at all, so x is not accessible inside the body.
Do this insteadWrite a capture list that names x, such as [&x] or [=x].
Common mistake Treating the capture list as decoration
Why it failsTwo lambdas with identical bodies can behave differently only because their capture lists differ.
Do this insteadRead the capture list first and ask, for each name the body uses, whether it is accessed by reference or by value.
Common mistake Forgetting that a bare ampersand or equals sign applies to every local name the body uses, not just the first one
Practice Reading Capture Lists
For each lambda below, state whether each named local the body uses is accessed by reference or by value: (a) [&] with a body using x and y; (b) [=x] with a body using x and y, where y is not captured by name; (c) [ ] with a body that uses only its parameter.
Hints
- A bare ampersand or equals sign applies to every local name the body uses.
- A named capture applies only to the name written after the sign.
- Empty brackets mean no local names are given to the body.
Key Takeaways
- A lambda expression is a notation that generates a function object, similar in kind to writing Less_than<int>{x}.
- The capture list sits first in square brackets and decides how each local name used in the body is reached.
- An ampersand means the body works through a reference to the original name, while an equals sign means the object receives a copy of the value.
- A bare sign applies to every local name the body uses, a sign followed by a name applies only to that name, and empty brackets give the body no local names.
- Choose the capture style by asking whether the body needs outside names at all, and whether it should see their current values or a snapshot.