Contents |

Recall from
the chapter on assignment
the series of expressions we evaluated to find
the *y*-value of a point on the line:

y = 5x - 3

First, we assigned values to the slope,
the *x*-value, and the *y*-intercept:

>>> m = 5 >>> x = 9 >>> b = -3

Once those variables have been assigned,
we can compute the value of *y*:

>>> y = m * x + b >>> y 42

Now, suppose we wished to find the *y*-value corresponding to
a different *x*-value or, worse yet, for a different *x*-value
on a different line. All the work we did would have to be
repeated. A *function* is a way to encapsulate all these operations
so we can repeat them with a minimum of effort.

First, we will define a not-too-useful function that
calculates *y* give a slope of 5,
a *y*-intercept of -3, and an
*x*-value of 9 (exactly
as above). We do this by wrapping a function around
the sequence of operations above.
The return value of this function is the computed *y* value:

def y(): m = 5 x = 9 b = -3 return m * x + b

There are a few things to note. The keyword `def` indicates
that a function definition is occurring. The name of this
particular function is *y*. The names of the things
being sent to the function are given between the parentheses;
since there is nothing between the parentheses, we don't
need to send any information to this function when we use it.
Together, the first line is known as the *function signature*,
which tells you the name of the function and how many values
it expects to be sent when it is used.

The stuff indented from the first line of the function definition
is called the *function body* and
is the code that will be evaluated (or executed) when the
function is used. You must remember this: *the function body
is not evaluated until the function is actually used*.

Once the function is defined, we can find the value of *y* repeatedly.
Let's assume the function was entered into the file named
*line.py*.

First we import the code in *line.py* with the from statement:

>>> from line import * # not line.py!

This makes the python interpreter behave as if we had typed
in the function definition residing in *line.py* directly into
the interpreter. Note, we omit the *.py* extension in the import
statement.

After importing the *y* function, the next thing we do
is use it:

>>> y() 42 >>> y() 42

The use of a function is known as a *function call*.
Most function calls look similar: the name of the function
to be called followed by a parenthesized list of information
to send the function so that it can do its job.
Thus, the parentheses after the *y* indicate that we wish to call
the *y* function and get its value. Because we designed the
function to take no information when called, we do not place any
information between the parentheses.

Note that when we call the *y* function again,
we get the exact same answer.

The *y* function, as written,
is not too useful in that we cannot use it to compute
similar things, such as the *y*-value for a different value of
*x*.
This is because we "hard-wired" the values of *b*, *x*, and *m*
in the definition of the function.
We can improve the *y* function by passing in the value of *x*
instead of hard-wiring the value to 9.

A hallmark of a good function is that it lets you compute
more than one thing. We can modify our *y* function to *take in* the
value of *x* in which we are interested.
In this way,
we can compute more than one value of *y*.
We do this by *passing* in
some information. This information that is passed to a function in a function
call is known as
an *argument*^{13}, in this case, the value of *x*.

def y(x): slope = 5 intercept = -3 return slope * x + intercept

Note that we have moved the variable *x* from the body of the function
to between the parentheses. We have also refrained from
giving it a value since its value is to be sent to the function
when the function is called.
What we have done is to *parameterize* the function to make it more
general and more useful. The variable *x* is now called a
*formal parameter* since it sits between the parentheses in
the first line of the function definition.

Now we can compute *y* for an infinite number of *x*'s:

>>> from line.py import * >>> y(9) 42 >>> y(0) -3 >>> y(-2) -13

What if we wish to
compute a *y*-value for a given *x* for a different
line? One approach would be to pass in the *slope* and *intercept*
as well as *x*:

def y(x,m,b): return m * x + b

Now we need to pass even more information to *y* when we call it:

>>> from line.py import * >>> y(9,5,-3) 42 >>> y(0,5,-3) -3

If we wish to calculate using a different line, we just pass in the
new *slope* and *intercept* along with our value of *x*.
This certainly works as intended, but is not the best way. One problem
is that we keep on having to type in the slope and intercept even if
we are computing *y*-values on the same line. Anytime you
find yourself doing the same tedious thing over and over,
be assured that
someone has thought of a way to avoid that particular tedium.
If so, how do we
customize our function so that we only have to enter the slope
and intercept once per particular line? We will explore
one way for doing this. In reading further,
it is not important if you understand all that is going on.
What is important is that you know you can use functions
to run similar code over and over again.

Since creating functions is hard work (lots of typing) and
Computer Scientists avoid unnecessary work like the plague, somebody
early on got the idea of writing a function that itself
creates functions! Brilliant! We can do this for our line problem.
We will tell our creative function to create a *y* function
for a particular slope and intercept! While we are at it,
let's change the variable names *m* and *b* to *slope*
and *intercept*, respectively:

def makeLine(slope,intercept): def y(x): return slope * x + intercept return y # the value of y is returned, y is NOT CALLED!

The *makeLine* function creates a *y* function
and then returns it. Note that this returned function *y* takes
one value when called, the value of *x*.

So our creative *makeLine* function
simply defines a *y* function and then
returns it. Now we can create a bunch of different lines:

>>> from line.py import * >>> a = makeLine(5,-3) >>> b = makeLine(6,2) >>> a(9) 42 >>> b(9) 56 >>> a(9) 42

Notice how lines *a* and *b* remember
the slope and intercept supplied
when they were created.^{14}
While this is decidedly cool, the problem is many languages (C, C++, and Java
included^{15})
do not allow you to define functions that create other functions.
Fortunately, Python does allow this.

While this might seem a little mind-boggling, don't worry. The things you should take away from this are:

- functions encapsulate calculations
- functions can be parameterized
- functions are defined so that they may be called
- functions return values

This last point is very important. Whoever calls a function needs to handle the return value either by assigning it to a variable or by passing it immediately to another function (nested function calls). Here is an example of the former:

y = square(x) z = square(y)

and here is an example of both the former and the latter:

z = square(square(x))

Both approaches yield identical results.

When a function calculates (or obtains) a value and returns it, we say
that it implements the *function* pattern. If a function
does not have a return value, we say it implements the
*procedure* pattern.

Here is an example of the *function* pattern:

def square(x): return x * x

This function takes a value, stores it in *x*, computes the square
of *x* and returns the result of the computation.

Here is an example of the *procedure* pattern:

def greeting(name): print("hello,",name) return "done"

We use a special return value to indicate procedures, the string
`"done"`

. Returning `"done"`

is meant to
indicate the return value should
not be used.

Almost always,
a function that implements the *function* pattern does not print
anything, while a function that implements the procedure
pattern often does^{16}.
A common function that implements the procedure pattern
is the *main* function^{17}.

A mistake often made by beginning programmers is to print a calculated value rather than to return it. So, when defining a function, you should ask yourself, should I implement the function pattern or the procedure pattern?

Most of the functions you will implement in this class follow the function pattern.

Another common mistake is to inadvertently implement a *procedure* pattern
when a *function* pattern is called for. This happens when the *return*
keyword is omitted.

def psquare(x): x * x

While the above code looks like the function pattern, it is actually
a procedure pattern. What happens is the value *x * x* is calculated,
but since it is not returned, the newly calculated value is thrown
away (remember the *throw-away* pattern?).

Calling this kind of function yields a surprising result:

>>> x = psquare(4) >>>print(x) None

When you do not specify a return value, Python will
supply one for you anyway. This supplied value is the special symbol
`None`

. Since *psquare* did not explicitly return a value,
`None`

was returned and assigned to the variable
*x*.
However, you should always use an explicit return.

Usually, the procedure pattern causes some side-effect to happen
(like printing). A procedure like *psquare*, which has no side-effect,
is a useless function.

Contents |