# Defining lambda functions

Consider the code below. It does two things.

def five():
return 5

First of all, it creates a new function whose output is 5. Second, it makes the variable five point at that function.

To be honest, though, that syntax is a little weird. If I want to bind five to the number 5, I write five = 5. If I want to bind it to the string 'five', I write five = 'five'. If I want to make five point to a function that returns 5, why can't I do something similar? Why not something like five = <pointer at a function that returns 5>? That's where the lambda keyword comes in.

## Syntax for defining lambda functions

A lambda expression directly evaluates to a pointer at a function, in the same way that an arithmetic expression directly evaluates to a number. lambda expressions follow this format:

lambda PARAMETERS: RETURN VALUE

For example lambda: 5 is a pointer at a function named "λ", which has no parameters and returns 5. You could bind the variable five to that pointer, by writing something like five = lambda: 5.

Now we have a variable that points at a function. We can call it in the usual way.

>>> five = lambda: 5
>>> five()
5

Meanwhile lambda x: x is a pointer at a function named "λ", which simply returns its input. You could bind the variable identity to that pointer, by writing something like identity = lambda x: x.

>>> identity = lambda x: x
>>> identity(4)
4

It's also possible to use the lambda keyword to make a function that takes in multiple arguments. For instance, consider average = lambda x, y: (x + y) / 2.

>>> average = lambda x, y: (x + y) / 2
>>> average(4, 8)
6.0

The takeaway? A lambda expression evaluates to a pointer at a function. So when we bind a variable to a lambda expression, that binds the variable to a pointer at a function. Then we can use the variable to call the function like we're used to.

## def statements versus lambda expressions

So ... what's the difference between a def statement and a lambda expression?

A def statement creates a function and binds it to a variable, whereas a lambda expression creates a function without binding it to a variable. It's up to you what to do with that lambda function — bind it to a variable, call it, whatever. In this way, def is just a shorthand way of making a function and binding it to a variable at the same time, without having to explicitly assign the variable using the = operator.

The most important difference is what you can actually do with them. Within a def statement you can assign local variables, evaluate chains of if / elif / else statements, and do iteration with while loops. A lambda expression is much more limited. You have your parameters before the colon, and your output after the colon. There's no room to do anything else, like variable assignment or boolean logic or iteration. Literally, a lambda expression lets you return a value, and do nothing else.

That means you can also rewrite lambda expressions as one-line def statements, if you so desire. This might help you get used to them, when you're just starting out. For example, these two snippets of code are basically the same:

add = lambda x, y: x + y
def add(x, y):
return x + y

Make sure you understand everything we've covered so far in this chapter, before reading on.

# Pyagrams with lambda functions

In the previous section we saw a few examples of how to draw lambda functions in pyagrams. Now it's time to go into more detail.

## Evaluating lambda expressions

Remember, a lambda expression literally evaluates to a pointer at a function. As we saw earlier the function is named "λ", and as with any function in a pyagram, we write its inputs in parentheses after its name.

add = lambda x, y: x + y

Each time you read the word lambda, it evaluates to a new pointer at a new lambda function, even if that results in having two identical lamdba functions. That's because each lambda expression is evaluated individually. This code, for instance, creates two separate lambda functions that both return 5.

f = lambda: 5
g = lambda: 5

Meanwhile this code creates only one lambda function. Then it copies the pointer down from f into g like we learned how to do when we first learned about functions and pointers.

f = lambda: 5
g = f

Also, a lambda function's parent frame is the frame you're in when you actually read the word lambda. (This is similar to how the parent of a normal function is the frame you're in when you read the word def.) In the code below, we evaluate the lambda expression in frame 1 so its parent is frame 1.

def make_square():
return lambda x: x ** 2

square = make_square()

First we define make_square in the global frame with an ordinary def statement.

Then we see the line square = make_square(), which we can't complete until we know the value of make_square().

We start a flag for the function call.

Then we fill in the flag banner. We look up make_square in the global frame to find that it's bound to a pointer at a function. Then we copy that pointer down into the flag banner.

With the flag banner complete, we can start frame 1 to evaluate the function call.

The only line in make_square is return lambda x: x ** 2. This creates a lambda function and returns a pointer to that lambda function. Notably, the parent of the lambda function is frame 1, since that's the frame we were in when we created it.

Last of all we go back to the global frame, where we can finish binding square to the value of make_square().

So in summary:

• Whenever you read the word lambda, it evaluates to a new pointer at a new lambda function, even if that results in having two identical lambda functions.
• The parent of the lambda function is the frame where you read the word lambda.

## Practice: evaluating the same lambda expression twice

Consider this modification of the example from the previous section:

def make_square():
return lambda x: x ** 2

square_1 = make_square()
square_2 = make_square()

As before, we open up frame 1 for the call to make_square, where we evaluate the lambda expression to get a new pointer at a new squaring function. The function's parent is frame 1, since that's the frame where we create it. Then we return the pointer from the call to make_square, and bind it to the variable square_1 in the global frame.

After we're back in the global frame, we get to square_2 = make_square(). Since we're just calling make_square again, we will basically repeat the same process from before.

First of all, we'll have to make a new flag for the second call to make_square().

Then we fill in the flag banner with the value of make_square. Again, we just look it up in the global frame since that's the first frame above the flag we're working on.

Once the flag is done, we can start a new frame. This one will be called frame 2, since it's the second frame other than the global frame. Like frame 1, it's for make_square. Frame 2's parent is the global frame, since it corresponds to a call to make_square, and the parent of make_square is the global frame.

When we evaluate the lambda expression for the second time, it produces a new pointer at a new squaring function, rather than referring to the same one from before. (Remember, you get a new function every time you read the word lambda.) Its parent is frame 2, since that's the frame we're in when we evaluate it. This pointer gets returned from the call to make_square.

Finally that pointer is bound to square_2 in the global frame. Notice how square_1 and square_2 end up pointing to different functions. That's because square_1 got bound to the pointer returned from the function call make_square(), and square_2 got bound to the pointer returned from a different function call make_square(). Each function call produced a new pointer and a new lambda function.

Check that you agree with this example, before you continue.

## Telling apart different lambda functions

Occasionally you'll be working with multiple lambda functions, and it can be hard to remember which is which. For instance, take a look at this extension of the same example from before:

>>> def make_square():
...     return lambda x: x * x
...
>>> square_1 = make_square()
>>> square_2 = make_square()
>>> square_3 = lambda x: 1 / 0
>>> square_2(4)
16

Here's the pyagram, right before the last line gets executed:

Now we're at the function call square_2(4). But there's a problem. Which lambda function does square_2 correspond to? Is it the one that squares its input, or the one that tries to divide by 0? The pyagram shows us square_2 is bound to a function named "λ", but it doesn't show us which lambda expression to look at in the code.

To deal with this, we're going to write a little number next to every lambda expression in our code. This will help us tell them apart. Then, when we draw the pyagram, we'll write the corresponding number next to every lambda function that we create. Let's start by annotating our code:

>>> def make_square():
...     return lambda1 x: x * x
...
>>> square_1 = make_square()
>>> square_2 = make_square()
>>> square_3 = lambda2 x: 1 / 0
>>> square_2(4)
16

Now when we draw lambda1 in our pyagram we'll name the function "λ1" rather than "λ", and when we draw lambda2 in our pyagram we'll name the function "λ2".

With this modification to the pyagram, it shows that square_2 in the global frame corresponds to lambda1 in our code. So when it's time to do the function call square_2(4), it's easy to see we should do 4 * 4 rather than 1 / 0.

## Practice: a lambda function as an argument

This may get confusing. If you find yourself lost, refer back to the procedure for drawing pyagrams. Draw the pyagram for this code:

def f(x, g):
y = g() + g()
return lambda y: x + y

x = 10
y = f(x - 9, lambda: x)(x)

First let's number the lambda expressions so we can tell them apart later. Here's the code after we annotate it, like we just learned in the previous section:

def f(x, g):
y = g() + g()
return lambda1 y: x + y

x = 10
y = f(x - 9, lambda2: x)(x)