Constructing Procedures using Lambda Constructing Functions using Lambda Expressions - SICP Comparison Edition"/>
| Original | Python | |
In using sum as in
section 1.3.1,
it seems terribly awkward to have to define trivial procedures such as
pi-term and
pi-next just so we can use them as
arguments to our higher-order procedure.
Rather than define pi-next and
pi-term, it would be more convenient to
have a way to directly specify the procedure that returns its input incremented by 4and the procedure that returns the reciprocal of its input times its input plus 2.We can do this by introducing the special form lambda, which creates procedures. Using lambda we can describe what we want as (lambda (x) (+ x 4)) and (lambda (x) (/ 1.0 (* x (+ x 2)))) |
In using sum as in
section 1.3.1, it seems
terribly awkward to have to define trivial functions such as
pi_term and
pi_next just so we can use them as
arguments to our higher-order function. Rather than define
pi_next and
pi_term, it would be more convenient
to have a way to directly specify the function that returns its input incremented by 4and the function that returns the reciprocal of its input times its input plus 2.We can do this by introducing the lambda expression as a syntactic form for creating functions. Using lambda expressions, we can describe what we want as lambda x: x + 4 and lambda x: 1 / (x * (x + 2)) |
| Original | Python |
| (define (pi-sum a b) (sum (lambda (x) (/ 1.0 (* x (+ x 2)))) a (lambda (x) (+ x 4)) b)) | def pi_sum(a, b): return sum(lambda x: 1 / (x * (x + 2)), a, lambda x: x + 4, b) |
Again using lambda, a lambda expression, we can write the integral procedure function without having to define the auxiliary procedure define the auxiliary function add-dx: add_dx:
| Original | Python |
| (define (integral f a b dx) (* (sum f (+ a (/ dx 2.0)) (lambda (x) (+ x dx)) b) dx)) | def integral(f, a, b, dx): return sum(f, a + dx / 2, lambda x: x + dx, b) * dx |
| Original | Python | |
| In general, lambda is used to create procedures in the same way as define, except that no name is specified for the procedure: (lambda ($formal-parameters$) $body$) The resulting procedure is just as much a procedure as one that is created using define. The only difference is that it has not been associated with any name in the environment. | In general, lambda expressions are used to create functions in the same way as function definitions, except that no name is specified for the function and the parentheses around the parameters and the return keyword are omitted. lambda $parameters$: $expression$ The resulting function is just as much a function as one that is created using a function definition statement. The only difference is that it has not been associated with any name in the environment. |
| Original | Python |
| (define (plus4 x) (+ x 4)) | def plus4(x): return x + 4 |
| Original | Python |
| (define plus4 (lambda (x) (+ x 4))) | plus4 = lambda x: x + 4 |
| Original | Python | |
| We can read a lambda expression as follows: ( lambda (x) (+ x 4)) // read as: ^ ^ ^ ^ ^ // the procedure of an argument x that adds x and 4 | We can read a lambda expression as follows: lambda x : x + 4 # ^ ^ ^ ^ ^ # the function of an argument x that results in the value plus 4 |
Like any expression that has a procedure function as its value, a lambda lambda expression can be used as the function expression in an application combination such as
| Original | Python |
| ((lambda (x y z) (+ x y (square z))) 1 2 3) 12 | print((lambda x, y, z: x + y + square(z))(1, 2, 3)) 12 |
Another use of lambda lambda is in creating local variables. We often need local variables in our procedures other than those that have been bound as formal parameters. We often need local variables in our functions other than those that have been bound as parameters. For example, suppose we wish to compute the function \[\begin{array}{lll} f(x, y)&=&x(1 + x y)^2 +y (1 - y) + (1 + x y)(1 - y) \end{array}\] which we could also express as \[\begin{array}{rll} a &=& 1+xy\\ b &=& 1-y\\ f(x, y) &= &x a^2 +y b + a b \end{array}\] In writing a procedure function to compute $f$, we would like to include as local variables not only $x$ and $y$ but also the names of intermediate quantities like $a$ and $b$. One way to accomplish this is to use an auxiliary procedure function to bind the local variables:
| Original | Python |
| (define (f x y) (define (f-helper a b) (+ (* x (square a)) (* y b) (* a b))) (f-helper (+ 1 (* x y)) (- 1 y))) | def f(x, y): def f_helper(a, b): return x * square(a) + y * b + a * b return f_helper(1 + x * y, 1 - y) |
Of course, we could use a lambda lambda expression to specify an anonymous procedure function for binding our local variables. The body of f f then becomes a single call to that procedure: function:
| Original | Python |
| (define (f x y) ((lambda (a b) (+ (* x (square a)) (* y b) (* a b))) (+ 1 (* x y)) (- 1 y))) | def f_2(x, y): return (lambda a, b: x * square(a) + y * b + a * b)(1 + x * y, 1 - y) |
| Original | Python | |
| This construct is so useful that there is a special form called let to make its use more convenient. Using let, the f procedure could be written as (define (f x y) (let ((a (+ 1 (* x y))) (b (- 1 y))) (+ (* x (square a)) (* y b) (* a b)))) | Python's declaration assignment cannot be explained as a shorthand for lambda expression/application. Thus we use the weaker formulation "a more convenient way"; this section is therefore a lot shorter in Python than in Scheme. A more convenient way to declare local variables is by using declaration assignments within the body of the function. Using a declaration assignment, the function f can be written as def f_3(x, y): a = 1 + x * y b = 1 - y return x * square(a) + y * b + a * b Variables that are declared with declaration assignments inside a function have the body of the immediately surrounding function as their scope.[2]$^,$[3] |
| Original | Python | |
|
The general form of a let expression is (let (($var_1$ $exp_1$) ($var_2$ $exp_2$) $\vdots$ ($var_n$ $exp_n$)) $body$) which can be thought of as saying \begin{array}{ll} \mbox{let}\ &var_1\ \mbox{have the value}\ exp_\ \mbox{and}\\ &var_2\ \mbox{have the value}\ exp_2\ \mbox{and}\\ &\vdots\\ &var_n\ \mbox{have the value}\ exp_n\\ \mbox{in}\ & body \end{array} The first part of the let expression is a list of name-expression pairs. When the let is evaluated, each name is associated with the value of the corresponding expression. The body of the let is evaluated with these names bound as local variables. The way this happens is that the let expression is interpreted as an alternate syntax for ((lambda ($var_1$ $\ldots$ $var_n$) $body$) $exp_1$ $\vdots$ $exp_n$) No new mechanism is required in the interpreter in order to provide local variables. A let expression is simply syntactic sugar for the underlying lambda application. We can see from this equivalence that the scope of a variable specified by a let expression is the body of the let. This implies that:
Sometimes we can use internal definitions to get the same effect as with let. For example, we could have defined the procedure f above as (define (f x y) (define a (+ 1 (* x y))) (define b (- 1 y)) (+ (* x (square a)) (* y b) (* a b))) We prefer, however, to use let in situations like this and to use internal define only for internal procedures.[4] |
This is the perfect place to introduce conditional statements, which are of course heavily used in the rest of the book. Their proper substitution model would be a bit technical and quite pointless to explain, so we limit the discussion of the substitution model to conditional expressions. We have seen that it is often useful to declare variables that are local to function definitions. When functions become big, we should keep the computation associated with the variables as restricted as possible. Consider for example expmod in exercise 1.26. def expmod(base, exp, m): return (1 if exp == 0 else (expmod(base, exp // 2, m) * expmod(base, exp // 2, m)) % m if is_even(exp) else (base * expmod(base, exp - 1, m)) % m) This function is unnecessarily inefficient, because it contains two identical calls: expmod(base, exp // 2, m) While this can be easily fixed in this example using the square function, this is not so easy in general. Without using square, we would be tempted to introduce a local name for the expression as follows: def expmod(base, exp, m): half_exp = expmod(base, exp // 2, m) return (1 if exp == 0 else (half_exp * half_exp) % m if is_even(exp) else (base * expmod(base, exp - 1, m)) % m) This would make the function not just inefficient, but actually nonterminating! The problem is that the declaration assignment appears outside the conditional expression, which means that it is executed even when the base case exp == 0 is met. To avoid this situation, we provide for conditional statements, and allow return statements to appear in the branches of the statement. Using a conditional statement, we can write the function expmod as follows: def expmod(base, exp, m): if exp == 0: return 1 else: if is_even(exp): half_exp = expmod(base, exp // 2, m) return (half_exp * half_exp) % m else: return (base * expmod(base, exp - 1, m)) % mThe simplest form of a conditional statement is if $predicate$: $consequent$-$statements$ else: $alternative$-$statements$ As for a conditional expression, the interpreter first evaluates the $predicate$. If it evaluates to true, the interpreter evaluates the $consequent$-$statements$ in sequence, and if it evaluates to false, the interpreter evaluates the $alternative$-$statements$ in sequence. Evaluation of a return statement returns from the surrounding function, ignoring any statements in the sequence after the return statement and any statements after the conditional statement. Python provides the keyword elif to avoid deeply nested else: if statements. Using elif, we can write the expmod function as follows: def expmod(base, exp, m): if exp == 0: return 1 elif is_even(exp): half_exp = expmod(base, exp // 2, m) return (half_exp * half_exp) % m else: return (base * expmod(base, exp - 1, m)) % m |
| Original | Python |
| (define (f g) (g 2)) | def f(g): return g(2) |
| Original | Python |
| (f square) 4 | print(f(square)) 4 |
| Original | Python |
| (f (lambda (z) (* z (+ z 1)))) 6 | print(f(lambda z: z * (z + 1))) 6 |