CS61A Lab 3:

I Heard You Liked Functions So I Put Functions In Your Functions So You Can Function While You Function

Exercise 1: Higher Order Functions

For every line that is marked with a "# ?" symbol, try to determine what Python would print in the interactive interpreter. Then check to see if you got the answer right.

```def square(x):
return x*x

def neg(f, x):
return -f(x)

# Q1
neg(square, 4)
_______________

# Q2
def first(x):
x += 8
def second(y):
print('second')
return x + y
print('first')
return second

f = first(15)
_______________

# Q3
f(16)
_______________

# Q4
def foo(x):
def bar(y):
return x + y
return bar

boom = foo(23)
boom(42)
_______________

# Q5
foo(6)(7)
_______________

# Q6
func = boom
func is boom
_______________

# Q7
func = foo(23)
func is boom
_______________

# Q8
def Troy():
abed = 0
while abed < 10:
britta = lambda: abed
abed += 1
annie = abed
annie += 1
abed = 20
return britta

jeff = Troy()
shirley = lambda : jeff
pierce = shirley()
pierce()
_______________
```

Exercise 2: Returning Functions

Define a function make_derivative that returns a function: the derivative of a function f. Assuming that f is a single-variable mathematical function, its derivative will also be a single-variable function. When called with an int `a`, the derivative will compute the slope of f at the point `a`.

The formula for finding the derivative of f at point a is

where h approaches 0. We will approximate the derivative by setting h to a very small number: `0.00001`. The closer you make h to `0`, the more accurate the derivative approximation will be.

```def make_derivative(f, h=1e-5):
"""Returns a function that is the derivative of f.

>>> square = lambda x: x*x
>>> derivative = make_derivative(square)
>>> result = derivative(3)
>>> round(result, 3)
6.0
"""
```

Note: make_derivative itself is not the derivative; the function it returns is.

Exercise 3: Helper functions

Now, we will visit the idea of helper functions. Sometimes, a function must perform a lot of computations -- so many computations, in fact, that it can get messy for the programmer to write. We can instead define a helper function within the parent function that will take care of some of the computations. The parent function then calls the helper function as needed.

Recall that a quadratic function is a function of the form:

The quadratic equation is a classic algebraic formula used for finding the roots of quadratic functions. Part of the quadratic equation involves finding the discriminant (the part under the square root).

where the discriminant, represented by the delta, is

Define a function find_root that, given the coefficients `a`, `b`, and `c` of a quadratic function, will compute a root of the function (normally, the quadratic equation has a "+" or "-" sign -- we will focus on the "+" for now).

Your implementation should use a helper function called discriminant, which also takes in the three coefficients `a`, `b`, `c`, and computes their discriminant. Remember, find_root is not returning the function discriminant; rather, find_root will call discriminant to help with some calculations.

```from math import sqrt

def find_root(a, b, c):
"""Returns one of two roots of a quadratic function.

Since there are two roots to quadratics, return the the larger
root. In other words, the + or - part of the quadratic equation
should just be replaced with a +

>>> find_root(1, 2, 1)
-1.0
>>> find_root(1, -7, 12)
4.0
"""
def discriminant(a, b, c):
"*** YOUR CODE HERE ALSO ***"
```

Exercise 4: Type-checking

Many procedures require a certain type of argument. For example, many arithmetic procedures only work if given numeric arguments. If given a non-number, an error results. For instance, pow('hello', 2) will result in the following error:

TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'

Suppose we want to write safe versions of procedures, that can check if the argument is okay, and either call the underlying procedure or return False for a bad argument instead of giving an error. (We'll restrict our attention to procedures that take a single argument.)

```>>> sqrt('hello')
Traceback (most recent call last):
File "", line 1, in
TypeError: a float is required
>>> type_check(sqrt, isnumber, 'hello')
False
>>> type_check(sqrt, isnumber, 4)
2.0
```

1.) Write type_check. Its arguments are a function, a type-checking predicate that returns True if and only if the datum is a legal argument to the function, and the datum.
For testing, you'll want the isnumber procedure: use the one here (you don't have to understand how it works):

```def isnumber(thing):
try:
int(thing)
except:
return False
return True
```

2.) We really don’t want to have to use type_check explicitly every time. Instead, we’d like to be able to use a safe_sqrt procedure:

```>>> safe_sqrt(’hello’)
False
>>> safe_sqrt(4)
2.0
```
Don’t write safe_sqrt! Instead, write a procedure make_safe that you can use this way:

>>> safe_sqrt = make_safe(sqrt, isnumber)

It should take two arguments, a function (you can assume it takes exactly one argument) and a type-checking predicate, and return a new function that returns False if its argument doesn’t satisfy the predicate.

Exercise 5: Functions in Functions

Define a function cycle which takes in three functions as arguments: f1, f2, f3. cycle will then return another function. The returned fuction should take in an integer argument n and do the following:

• return a function that takes in an argument x and does the following:
• if n is 0, just return x
• if n is 1, apply the first function that is passed to cycle to x
• if n is 2, the first function passed to cycle is applied to x, and then the second function passed to cycle is applied to the result of that (i.e. f2(f1(x))). If n is 3, apply the first, then the second, then the third function (i.e. f3(f2(f1(x))))
• if n is 4, apply the first, then the second, then the third, then the first function (i.e. f1(f3(f2(f1(x)))))
• And so forth

Hint: most of the work goes inside the most nested function.

```def cycle(f1, f2, f3):
""" Returns a function that is itself a higher order function
>>> add1 = lambda x: x+1
>>> times2 = lambda x: 2*x
>>> add3 = lambda x: x+3
>>> identity = my_cycle(0)
>>> identity(5)
5
4
>>> do_all_functions = my_cycle(3)