Function Objects and Lambda Expressions
Together, they explain why function objects are especially useful as arguments to algorithms, which may call the argument many times over a sequence of elements.
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 Counting Problem That Needs a Rule
Suppose you have a list of words and want to count how many come before the word 'm' in alphabetical order. A general counting algorithm can walk through the elements of the list, but it does not know your rule. It asks a question about each element, and the question itself has to come from somewhere. The question you need is not 'is this element equal to some value?' It is 'is this element less than a particular string?' This article looks at how a function object can supply that question to a general algorithm, and why that approach is useful.
What do you think happens?
Given the words apple, kiwi, and mango, how many come before 'm' in alphabetical order?
Reveal answer
2
apple and kiwi come before 'm'. mango does not, because 'm' is a prefix of 'mango' and therefore sorts before it. The same rule will be built into a function object in the sections that follow.
Tracing the Word List Element by Element
Before looking at any code, follow what a counting algorithm does with the three words. It visits each element in turn, asks the same question about it, and adds one to the count whenever the answer is yes. The question stays the same for every element. Only the element being examined changes.
Walking the list with the question 'is this word less than m?'
Count the words in the list apple, kiwi, mango that are less than the string 'm'.
- 1Visit apple
Ask whether apple is less than 'm'. It is, so the count goes from 0 to 1.
- 2Visit kiwi
Ask whether kiwi is less than 'm'. It is, so the count goes from 1 to 2.
- 3Visit mango
Ask whether mango is less than 'm'. It is not, because 'm' is a prefix of 'mango' and sorts before it. The count stays at 2.
The count is 2.
Function Objects Carry Their Value
A function object is an object that can be used as if it were a function. The source describes Less_than<string>{s} as an object that compares to the string called s. The object holds that string inside itself. When you create it, the value it compares against travels with it.
This is the main reason function objects are useful. Without them, a separate ordinary function would be needed for each value you might want to compare against, and each value type might need its own function as well. The alternative would be to keep the value in a global variable that the function reads. Function objects avoid both problems. You do not write a separate function for each value and type, and you do not introduce global variables to hold the values.
The diagram below contrasts the two approaches. In the first column, each comparison value needs its own function, and the value is kept in a global variable that the function reads. In the second column, one object holds both the value and the operation, so nothing outside the object needs to remember what the comparison value was.
Why Simple Function Objects Are Efficient
Carrying data is only half of the story. The source also states that, for a simple function object such as Less_than, inlining is straightforward. Inlining means the body of the operation can be placed directly where it is used, instead of being reached through a separate function call at run time. Because of this, a call of Less_than is far more efficient than an indirect function call.
The two properties support each other. Carrying data makes the object convenient, and its efficiency makes it practical. Together, they explain why function objects are especially useful as arguments to algorithms, which may call the argument many times over a sequence of elements. A small cost per call adds up across a long sequence, so avoiding an indirect call on every step matters.
Policy Objects and General Algorithms
A policy object is a function object that defines the meaning of a key operation in a general algorithm. The word policy describes the role of the object. The algorithm is general: it knows how to walk a sequence and count matches. The policy object decides what counts as a match. Changing the policy changes the meaning of the operation without changing the algorithm.
Read the diagram from top to bottom. The algorithm owns the walk through the sequence and the bookkeeping of the count. The policy owns the answer to the question of whether an element matches. The algorithm never needs to be rewritten to answer a different question; only the policy passed to it changes.
Code: Passing a Policy to count_if
The following example is an original illustration built from the source's idea. It defines a function object that carries a string and compares against it, then passes that object to the count_if algorithm. The class is a template, so the same pattern works for other types.
n is 2- Less_than stores the string s inside itself when it is created.
operator()is the call thatcount_ifmakes for each element.- The comparison rule lives entirely in the Less_than object passed to
count_if.
The output is 2 because apple and kiwi come before 'm', while mango does not. The count_if algorithm is general. The comparison rule lives entirely in the Less_than policy object, so the same algorithm could be given a different policy for a different question, such as a different string to compare against.
Implicit Notation with Lambda Expressions
The example above has a drawback. You have to define Less_than separately from its use, and that separation can feel inconvenient when the object is needed for only one call. The notation for implicitly generating function objects addresses this. It lets you describe the object at the point where it is needed instead of writing a separate definition first. In C++ this notation is called a lambda expression.
n is 2- The lambda plays the role of the Less_than object, written in place.
- The result is the same as the separately defined Less_than version.
Common Mistakes With Function Objects
Common mistake Writing a separate function for every comparison value
What it looks likeOne function for less than m, another for less than z, each reading its own global variable.
Why it failsThis is the approach function objects are meant to avoid. It multiplies functions and introduces global state for every value.
Do this insteadCreate a function object that holds its value, such as Less_than<string>{"m"}, and pass that object to the algorithm.
Common mistake Assuming the algorithm decides what counts as a match
What it looks likeExpecting count_if to know that the words should be compared alphabetically against 'm'.
Why it failsThe algorithm is general. It only walks the sequence and counts. The rule comes from the policy object.
Do this insteadPass a policy object whose operation defines the match you want.
Common mistake Expecting the lambda to behave differently from the named object
What it looks likeThinking the inline lambda is a different kind of thing that works by another mechanism.
Why it failsThe source describes the implicit notation as a way to generate the same kind of function object at the point of use. It changes where the definition is written, not how the object behaves.
Do this insteadTreat the lambda as a shorter way to write the same function object, and reason about it the same way.
Trace It Yourself
A list contains the words zebra, lemon, and banana. Using the same counting pattern with a function object that compares against 'm', how many words does count_if count, and which words are counted?
Hints
- Ask the question 'is this word less than m?' for each word in order.
- Remember that a word is counted only when the answer is yes.
- banana and lemon sort before 'm'; zebra does not.
Key Takeaways
- A function object carries its comparison value inside itself, so you do not need a separate function per value or global variables to hold the values.
- A simple function object such as Less_than can be inlined, which makes calling it far more efficient than an indirect function call, especially when an algorithm calls it many times.
- A policy object is a function object that defines the meaning of a key operation in a general algorithm such as
count_if; changing the policy changes the operation without changing the algorithm. - The implicit notation, called a lambda expression in C++, lets you generate a function object at the point of use instead of defining it separately first, without changing how the object behaves.