Problem 3: Applying Lambda Functions (200 pts)
We can do some basic math now, but it would be a bit more fun if we could also call our own user-defined functions. So let's make sure that we can do that!
A lambda function is represented as an instance of the LambdaFunction
class. If you look in LambdaFunction.__init__
, you will see that each lambda function has three instance attributes: parameters
, body
and parent
. As an example, consider the lambda function lambda f, x: f(x)
. For the corresponding LambdaFunction
instance, we would have the following attributes:
parameters
-- a list of strings, e.g.['f', 'x']
body
-- anExpr
, e.g.CallExpr(Name('f'), [Name('x')])
parent
-- the parent environment in which we want to look up our variables. Notice that this is the environment the lambda function was defined in.LambdaFunction
s are created in theLambdaExpr.eval
method, and the current environment then becomes thisLambdaFunction
's parent environment.
If you try entering a lambda expression into your interpreter now, you should see that it outputs a lambda function. However, if you try to call a lambda function, e.g. (lambda x: x)(3)
it will output None
.
You are now going to implement the LambdaFunction.apply
method so that we can call our lambda functions! This function takes a list arguments
which contains the argument Value
s that are passed to the function. When evaluating the lambda function, you will want to make sure that the lambda function's formal parameters are correctly bound to the arguments it is passed. To do this, you will have to modify the environment you evaluate the function body in.
There are three steps to applying a LambdaFunction
:
- Make a copy of the parent environment. You can make a copy of a dictionary
d
withd.copy()
. - Update the copy with the
parameters
of theLambdaFunction
and thearguments
passed into the method. - Evaluate the
body
using the newly created environment.
Hint: You may find the built-in
zip
function useful to pair up the parameter names with the argument values.
def apply(self, arguments):
"""
>>> from reader import read
>>> add_lambda = read('lambda x, y: add(x, y)').eval(global_env)
>>> add_lambda.apply([Number(1), Number(2)])
Number(3)
>>> add_lambda.apply([Number(3), Number(4)])
Number(7)
>>> sub_lambda = read('lambda add: sub(10, add)').eval(global_env)
>>> sub_lambda.apply([Number(8)])
Number(2)
>>> add_lambda.apply([Number(8), Number(10)]) # Make sure you made a copy of env
Number(18)
>>> read('(lambda x: lambda y: add(x, y))(3)(4)').eval(global_env)
Number(7)
>>> read('(lambda x: x(x))(lambda y: 4)').eval(global_env)
Number(4)
"""
if len(self.parameters) != len(arguments):
raise TypeError("Oof! Cannot apply number {} to arguments {}".format(
comma_separated(self.parameters), comma_separated(arguments)))
"*** YOUR CODE HERE ***"
Use Ok to test your code:
python ok -q lambda_apply
After you finish, you should try out your new feature! Open your interpreter and try creating and calling your own lambda functions. Since functions are values in our interpreter, you can have some fun with higher order functions, too!
python repl.py
> (lambda x: add(x, 3))(1)
4
> (lambda f, x: f(f(x)))(lambda y: mul(y, 2), 3)
12