The following content is provided under a Creative
Commons license.
Your support will help MIT OpenCourseWare continue to
offer high quality educational resources for free.
To make a donation or view additional materials from
hundreds of MIT courses, visit MIT OpenCourseWare at
ocw.mit.edu.
PROFESSOR: For those of you who are unaccustomed to seeing
it, that blue stuff through the window is called sky.
OK.
I left you last time with a question.
I'd shown you a bisection search implementation, or I
should say putative implementation of the square
root, and told you that it was flawed, and asked you to think
about what was wrong with it.
So first, I'd just like to take a poll.
How many of you know what the problem is, or at least what a
problem is?
OK well that's a good start.
And I won't ask how many of you don't know, because I
presume that's the rest of you.
So let's look at it now.
I'm not going to ask you the answer, because I want to show
people how to find it.
So here's a slightly simplified version of it.
Just get rid of this stuff here, so it doesn't confuse
the picture.
So this is roughly the one we looked at on Tuesday, but I
just took out some print statements and things and
simplified it.
And let's just run it.
And we'll run it with trying to find the
square root of 0.5.
See what happens when we run it.
Well, not much happens when we run it.
Actually quite a lot is happening.
We just can't see it.
So this program is running longer than I expected it to.
So if you go to the keyboard and you hit Control and C. It
will interrupt the program.
It generates what's called a keyboard interrupt,
and it stops it.
And it tells us where it happened to stop it.
It happened to stop it in line seven--
the test of the while loop.
And the problem is, the program just seemed to be
running forever.
So despite my, perhaps persuasive, argument last time
about the decrementing function, there's clearly a
flaw in my logic.
And, in fact, it does not always terminate.
So what do we do about it?
Well this is the main trick.
And one of the things I need you to understand this
semester is perhaps the most important thing you'll learn
is how to debug programs.
Too many people think the hard part is done when they write
the code the first time.
No, that's actually the easy part.
The hard part is actually getting it to work.
So the thing you need to learn is how to debug code, and the
nice thing is it's a transferable skill.
It's also fine for debugging lab experiences or family
members or anything else that can be seriously awry.
So the way I do it when I program is typically with
print statements.
So the fact that the program was running forever, suggests
that I'm not exiting the while loop.
So clearly, I need to print something in the while loop.
And the only three variables it seems to be using are
answer, low, and high.
Those are the three that change.
So let's see what the value is.
Notice, by the way, that I actually went to the trouble
to type ans equals ans, low equals low, et cetera.
A lot of people would just say, OK I'm going to print and
they'll ans comma low comma high.
And then when they run the code they'll
forget which is which.
The most common problem that people have in debugging
programs is that they are lazy.
They think they're saving themselves work, and in fact
they're creating work.
So my first piece of advice to you as
debuggers is don't be lazy.
Maybe you heard this from your mother at some point in life,
or your father.
But now you're hearing it from me.
Try and just do it right the first time.
So let's run it and see what happens now.
Well at least it's printing some output.
And it's chugging away and chug-- uh-oh--
so now, we see we have a real problem.
We've reached a fixed point, where every time through the
loop, nothing is changing.
Well the first time we go through the loop and nothing
changes we know we're in trouble, because nothing's
going to ever change.
And therefore, we're going to be in the loop forever.
So we see, I've gone to a stage where
everything equals 0.5.
And now if I go back and look at the code, and I ask myself
the question well what happens when everything is 0.5, and I
can see the problem.
It's this statement here.
Yep, turn it around maybe.
But that's never going to change now, because it's 0.5
plus 0.5, divided by 2 is 0.5.
And it'll just stay there forever.
So that's my problem.
I have to somehow change my code now, so that
this doesn't happen.
So what is the problem?
Now somebody can tell me, simply.
What is the problem here?
What was the flaw in my reasoning when I first set
this program up?
Yeah?
AUDIENCE: [INAUDIBLE]
PROFESSOR: Louder please?
AUDIENCE: You don't have a high minus low is less than or
equal to epsilon.
PROFESSOR: So the comment was I don't have a high minus low
is less than or equal to epsilon.
True, but that's not really the real flaw.
Yeah?
AUDIENCE: The fraction is greater than the original
fraction, so the solution is not in the search space.
PROFESSOR: Exactly.
So the answer is the problem was that I did a search in a
region, and the answer wasn't in that region.
Because the square root of 0.5 does not lie
between 0 and 0.5.
Silly me, when I thought about it, I didn't think of finding
the square root of numbers less than 1.
So what's a simple fix?
Well what I can do is the following: I'll go back and
say high is going to be the max of x and 1.
So now I'm going to ensure that the square root actually
does lie in the region I'm searching.
I hope.
Let's run it.
Ah.
All right, well I got to some stuff at the end which you
shouldn't worry about, but it found something that I guess
is a good enough answer.
We'll get rid of that code I put in this morning which
we'll get to this later.
OK so I've now fixed the program.
Everyone with me on that?
Any questions?
And the thing to understand is conceptually what was wrong
with my reasoning, that I'm doing a search in a region
where the answer doesn't lie.
So I'm not going to find it.
And the other thing to understand is my systematic
way of finding the bug.
Now I confess I knew the bug was there when I wrote the
code, so I kind of cheated with the debugging.
But even if I hadn't known, this is what
I would have done.
I would have put in that print statement.
All right, so now we have actually a pretty good piece
of code for finding square roots.
And as we looked at on Tuesday, I can use the same
piece of code.
I can modify it to get cube roots, or fourth roots, or
fifth roots.
And so I have a general framework for doing things.
But it's pretty unsatisfying in that sense, because let's
look at it.
If I wanted to find the square root of some number other than
0.5, I have to go and edit the code, replace the assignment
to x by whatever I'm trying to do.
If I want to do cube roots I have to cut and paste and edit
and do things.
There's no very good way to now embed this piece of code
inside a larger computation.
Imagine that I've got some 10,000 line program that needs
to find the square root six or seven times, well now I'm
going to have six or seven copies of this code in my
program, for every time I need the square root.
Clearly not what you want to do.
In general, having more code is a bad thing.
So it's not like you're given an essay to write and someone
tells you it's got to be 5,000 words, and you just sweat
blood trying to figure out how to stretch it to be that long.
In code, it's the other way around.
Most of the time we want to make it shorter not longer.
And the reason we want to do that is the difficulty of
getting code to work grows, maybe even grows
quadratically, or worse but the size of the code.
So the more code you have, the harder it is
to get it to work.
So one of the things good programmers learn to do is
write less code.
And so we don't measure productivity of a programmer
by the number of lines of code they produce each day, but we
measure it by the amount of functionality they
produce each day.
And we give them bonus points if they achieve the desired
functionality with less code.
So let's talk about how we can write less code
and accomplish more.
Well to do that, we're going to look at a new language
mechanism--
actually not new, but new to this class--
called a function.
But before we do that, I want to pull back and talk about
what it is we hope to accomplish by introducing
functions into our programming language.
We want to provide a mechanism that provides for two things:
decomposition and abstraction.
What decomposition does, is it creates structure.
It allows us to break our program up into something
called modules.
And the module we'll focus on today is function, but later
we'll see there's another important kind of module in
Python called the class.
And the advantage of a module is it should be self-contained
and reusable.
So it's a self-contained unit of functionality that can be
used in multiple contexts.
Abstraction suppresses details.
It allows us to use a piece of code as if it
were a black box.
That is, something whose interior details we can't see,
don't need to see, and shouldn't even want to see.
We only need to understand what it does,
not how it does it.
And that lets us use code that other people
have written easily.
And, in fact, use code that we have written easily.
It's one of those few occasions where I think Thomas
Gray was right, when he said, "ignorance is bliss."
Sometimes knowing less is better.
All right, so let's look at the way functions work.
The functions let us break code into
reusable, coherent pieces.
Now we've already looked at similar kinds of things.
When we looked at say floating point numbers, and we wrote
operations like plus or divide, whatever, we didn't
worry about how they were actually
implemented in the machine.
We said OK they do something, they're kind of like dividing
real numbers, let's not worry about the details.
We do that with a lot of things.
We looked at strings.
We concatenated strings.
Well we didn't worry about how did Python
go about doing that.
We just assumed it did it, and it had the meaning
we wanted it to.
What functions let us do is extend the language in some
sense by adding new primitives that we can use just the way
we used the built-in primitives.
So let's look at an example here.
I've written a very simple function.
Does something that we actually did already when we
looked at square roots.
It's a function called within epsilon.
And let me comment this out while I'm thinking about it,
so we don't have to live with it later.
And I now want to walk you, slowly, through what this
function does.
So at the start, it uses the keyword Def, short for define.
Following that is a name.
I chose the name within epsilon.
You can choose any name you want for a function.
I'm strongly encourage you to choose mnemonic names, that is
to say names that have a meaning.
So in some sense you see it says within epsilon, and you
know what it does already.
Following that, it has three things called formal
parameters.
I'll come back to in a minute, what that means.
And then after that, it's got something called
the function body.
So we see that a function has a name, it has parameters, and
it has a body.
The body is the code that's part of the function.
In the body, you can write any code you want.
Plus, there's something you can't write outside of a
function, called return.
That's a special command that says whoever calls me has
called me to have me compute a value.
I'm going to return the value that this person would want.
And then here we see something that's very important.
This is where we get abstraction, and that's the
specification of the function.
And it says here, there are two pieces to it.
One that its parameters x, y, and epsilon, are all floats.
And furthermore, epsilon is greater than 0.
You can imagine this is important, and it returns true
if x is within epsilon of y.
Otherwise it will return false.
If I want to use within epsilon, I don't need to look
at the code.
I look instead at the specification.
Now here where the code is one line, maybe I haven't gained a
lot by looking at the specification
instead of the code.
But you can imagine if the code were 1,000 lines, I'd
much rather read the specification than the code.
We'll also see for other reasons later why it's in fact
dangerous to look at the code.
How do I use it?
I use it by invoking it.
So I could, for example write something like print within
epsilon, of two, three, one.
What's it going to print?
Pardon?
Why is it going to print an error do you think?
AUDIENCE: Because you haven't put epsilon.
PROFESSOR: Ah, typed it wrong.
Thank you.
You're correct.
It would have printed an error.
Now what will it print?
AUDIENCE: [INAUDIBLE]
PROFESSOR: Sure enough.
I could also, if I chose, write something like val
equals that, then if I want I could print val.
Now it's going to print false.
So within epsilon is just like plus or something else, does
some computation, returns a value.
I can use that value any place I could have used an
expression.
Now one more thing to look at with this.
Suppose I don't return anything.
Anyone want to guess what it's going to do now?
I point this out, because this is a very common error.
People write lots of code, calculate some wonderful value
and then forget to return it.
What's it going to do now?
Well let's run it and see.
That, by the way, is a good habit to get into.
It's going to return the special value none.
Remember we looked at that earlier, meaning I
don't have a value.
So if you see in your code some none popping up where you
don't expect it to, it's probably because you forgot to
return a value.
So just keep that in mind.
All right, now there's a big advantage of this.
Once I've written this code I can now anywhere I want call
within epsilon, and I don't have to duplicate the code.
I only do it once.
As I said earlier maybe I'm not gaining much, because the
body is so short.
On the other hand, I'm still gaining something.
Notice that when I look at the code down here, it's easy to
read, I'm printing within epsilon two, three, and one.
And I don't have to decode this and tell me that that's
what that's doing.
So if I have a function and I choose the names properly,
code that uses the function is much easier to read.
And that can be a big value.
All right, let's look at another example.
So here I've got this function, f.
I've chose a non-mnemonic name, because there isn't much
meaning to this function.
What f does, it is a formal parameter x.
It sets x to x plus 1.
Then it prints x and returns x--
pretty boring.
So let's see what it does here.
So now I'm going to set x to three, set z, or zed if you
happen to be Canadian, to f of x.
And then print the values of z and x.
Also in f, before I return x, I'm going to print it.
So let's see what happens when I run this one.
It prints four, four, and then three.
All right, what's going on?
Why did it do that?
Well it's pretty easy to see why it printed four here,
because I called f of x with an x equal to 3, and then I
incremented it by one, and then I printed it.
It's probably also easy to understand why z was four,
because I returned the value of x here, which was four and
it printed it.
But why is this x three?
And the answer is this x and that x have nothing to do with
each other.
Right?
I could just as easily have chosen some other value for
the formal parameter, say George, and said George is
equal to George plus 1, print George, return George.
There is no relation between the name of the formal and, in
this case, x defined in the calling environment.
So now let's think about that by working slowly and
carefully through what happens when we call a function.
So the first thing that happens at the call, and I'll
just work it through this one, is the formal parameter, x in
this case, is bound--
and I'll come back to what binding means, that's a
critical concept here--
to the value of the actual parameter.
So these are important terms, actual and formal, which in
this case, also happens to be called x.
But what's happening here, is upon entry of a function, a
new scope is created.
What's a scope?
A scope is a mapping from names to objects.
So if we look at what's going on over here, we can draw a
little picture.
Well before I draw a picture, I'm going to look at a
slightly more complicated example.
Well, yeah let's do that.
This one is not in your handout, but it is
illustrative of, I think, what's really going on here.
Here I've got another beautifully named function, in
this case f1, and inside it, I've defined another function,
called g, which takes no arguments.
I've set x to abc.
Then I haven't shown you these assert statements yet, or
haven't talked about them.
Assert is a command in which the keyword assert is followed
by an expression that evaluates to
either true or false.
If it evaluates to true, it does nothing.
It just continues.
If it evaluates to false, it stops your
program dead in its tracks.
So I've just used it here as a trick to make my program stop
when I run it.
In general, you'll find that I use asserts quite a lot.
So for example, in the next piece of code, which
is called find root.
It takes the root, is it square, or cube, whatever, the
value, and epsilon.
It assumes that powers, and int, and val, and epsilon
float, in the specification.
And then you'll notice, I start by putting in an
assertion here.
And what I'm asserting is that the actuals to which these
formals are bound, have the properties the specification
says they do.
This is what's called defensive programming.
In principle, I shouldn't have to do that, because in
principle, nobody should call this with incorrect values.
But, in fact, it can happen.
Programmers occasionally make mistakes.
And so I'm protecting myself by checking that the
assumptions are met, and if they're not, my
program will just stop.
Then I can go hunt down the fool that called it with the
wrong parameters--
probably myself.
So asserts are good for that, and I'll use them a lot for
these kinds of things.
I'll also use them when I think I know what value
something should be in a program at some point, and I'm
not sure it really is.
I'll assert that it has the value I think it is.
I'll assert that x is six, if I think it's going to be six.
And then my program will conveniently stop for me if
it's not true.
All right so that's assert.
Other than that, I think there's nothing here you
haven't seen before.
So what's going to happen, we're going to step through
this piece by piece.
So initially, as we look at it, we enter the main body of
the program, which is not wrapped in a function.
So what IDLE will do, or the interpreter will do, is it
will start by executing each def.
But executing a def doesn't do anything, but put some names
in the environment.
Then it will go and start actually running and
interpreting the code that's not nested inside a function.
So the first thing that will happen is the interpreter will
build for me what's called the scope.
I've already mentioned, that's a mapping
from names to objects.
So in the outermost scope, it will first find the name f1.
F1 it will tell me that f1 maps to an object that happens
to be a function.
So it will come over here--
and I'm just going to draw some picture, we'll assume
that's the memory of the computer--
and it will map to something that happens to be a bunch of
code, if you will.
All right?
It will then stop.
It will then notice that it's got, at the outermost level a
variable called x.
And that will map to an integer, which will initially
have no value in it.
And then after the assignment, it will now be bound to the
object three.
It will then create another object z, but before it can
bind a value to it, it will invoke the function f1.
Now the interpreter starts to execute f1.
When it does that, it will create another scope.
So this is the main scope.
It will next create a scope called the f1 scope.
In that, it will have another name g, which will be
bound to some code.
It will have a name x, which will be initially
bound to the actual.
So in this case, it will be bound to the object three.
We'll then eventually do a print.
It will involve g, which will now create the g scope.
And the g scope will create a name x, which in this case
will be bound to the string abc.
It will then start executing g, and it will stop.
So let's see what that looks like.
Sure enough, it got an assert false, gave
an assertion error.
What I can do now is go up to this debug here, and go to
what's called a stack viewer.
Each of these scopes is what's called a stack frame.
Now why are they called stack frames?
Because when we do it, we begin with the main scope.
We call f, and we get the scope.
f calls g and we get the g scope.
When g completes, which alas it doesn't because of the
error, it pops the stack, and gets rid of the g scope.
And now the stack is the f and the main, and then when f
completes, it will have just the main.
So it's last in, first out, which is typically called a
stack in computing--
or a LIFO, if you're a course 15 major, and do accounting.
So let's look at the stack viewer.
And I apologize for the small type font, but I was unable to
make it look bigger.
So it says at the top we've got an assertion error.
And then you'll note it's got three stacks: the main, the f1
stack, and the g stack.
I forgot I called it f1, not f.
Then I can inspect them further.
So the g stack has local and global variables.
The local variables include x, which is equal to abc.
Globals we'll get to later.
If I look at f1, it also has a local called x, but its value
is now four, not abc.
And it has a value called g, which is a
function, as we discussed.
And if I look at main, it has a bunch of things, but it has
everything that's available in the interpreter, which because
we've looked at within epsilon it's there.
But you'll notice it's x is 3.
All right?
So the stack viewer can be very handy, to look at what
you've got, when you've got a bunch of calls.
Now if we go back to our code here, and we'll take this out.
And suppose what we do is we assert false here.
Now if we look at the stack viewer, we see that we have f1
in main, but g is no longer there.
It's gone.
All those variables don't exist anymore, because I'm no
longer in g.
This is the nice thing, because it means if you call
something 1,000 times, it doesn't
use up all your memory.
Every time it's finished, it gets rid of what
it no longer needs.
All right?
Does that make sense?
This is an important thing to get--
yeah, thank you, question.
AUDIENCE: --the assertion.
PROFESSOR: Where did I--
AUDIENCE: Where did you put this other assertion, when you
just changed--
PROFESSOR: Ah, where did I put the other assertion?
If we look at the code, you'll see I put it after I call g,
and g is by now returned, but before I left f1, which is why
the f1 stack is still present.
That makes sense to you?
Which stacks exist, which stack frames exist, depends
upon which functions are still active.
Yeah?
AUDIENCE: How come you don't need a
return under the g function?
PROFESSOR: Oh, because there's not going to be anything
interesting.
It's useless.
Right?
Why don't I need a return under g?
Well if I wanted it to do something useful, I would need
to return something.
But I'd probably also want to pass it some arguments, rather
than have it take no arguments, as well.
So it's here just to be the simplest thing I could put
that created a stack frame.
But don't try and interpre it as being anything meaningful.
Yeah?
AUDIENCE: Would you run into problems assuming that g did
something to x and then returned it?
Would you run into any problems that you named the
variable the same?
You know, that you used x twice?
Would you want to--
PROFESSOR: No.
If an x exists, or any variable exists within a
function body, when you leave that function, that variable
is gone forever.
These are just names.
They have no intrinsic meaning.
So one of the ways to think about it, and we'll see this
later when we get to classes--
a lot later.
You could, if you wanted, think about this as really the
name g.x, and you could really think of this as the name of
f1.x, and you could think of this as the name of main.x,
indicating that they're really not the same.
But it would be kind of a pain to write them all that way.
OK?
So different scopes have different names
available to them.
You can use the names in the scope, and you have to keep
track of what they mean.
OK?
Any other questions?
These are great questions, and I really do appreciate them.
Yeah?
AUDIENCE: Does this also happen with four loops?
Like if you say 4x in range something,
can you use x later?
Or is it x--
PROFESSOR: You can use x later.
AUDIENCE: Okay so--
PROFESSOR: x will be available outside the loop.
This was if you said for x in something, is x available
outside the loop?
Yes.
And in fact, you'll often want to test what the final value
of x is, when you leave the loop.
OK, the next thing on your handout, and I'm not going to
go over it, is using functions to implement something that
finds roots.
There's no real point in my walking you through
this code in class.
I did include it in the handout.
And by the way, the handouts are all available after
lecture, online.
Is that, I think you should work through in your own, and
make sure you understand it, to get a sense of how
functions work.
And it's certainly related to the current problem set, which
would be another good reason to work through it--
the problem set that will be posted today--
the new problem set, PS 2.
Note again how careful I am about the specifications.
And I should point out something interesting, if I
type find root, open para-- well let's do this here.
Let's clear things up.
Let's get rid of things that will cause
the program to halt.
Notice that when I type find root open, open paren, it's
given me the values--
the names of the formal parameters, which I've chosen
in such way that will remind me what their value should be.
And it's also given me part of the specification, the piece
in the triple quotation marks to tell me the rules I'm
supposed to be following here on these things.
So it's a very handy thing.
And as you use IDLE, you'll get used to the fact that this
is a convenience.
All right, work your way through that code.
Make sure you know what it does.
Finally, today I want to switch gears, and talk about
something else.
Up till now, all of the programs we've looked at have
been numeric--
they've played with numbers.
And I've done that, because I assumed you guys all had some
intuition about numbers.
I've use strings as a primitive data element to
print things, but we haven't done anything very interesting
with strings.
However strings are indeed quite interesting, in that
they're the first non-scalar value we've looked at.
You'll recall non-scalar values are values that can be
decomposed.
So if we now look at the code again, I've got this little
piece of code called sumDigits.
So before, the for statement we looked at
was for x in range.
Well you can apply it to a for statement to any type that has
a way to enumerate its elements.
So for c in STR, actually of 1952, so I've taken the number
1952 and converted it to a string so it will now be quote
one nine five two, I can now do something to every
character in that string.
And what I'm doing is converting it back to an int,
and then adding it.
So this will give me the sum of the digits.
17.
This is a very convenient mechanism, and you'll use for
a lot, this way.
You'll use it in fact more for this sort of thing then you
will for ints.
Now I can also select values.
So if I look at--
I don't know what's going on here.
Every once in while when you go back and forth between the
editor and the shell, the shell hangs and you have to go
try it again.
If I go to s equals abc, I can look at individual elements of
s, for example s sub 0, which will be a.
I can also look at slices of s.
So for example s from 0 to one.
That's interesting.
What is it doing?
Now try and infer.
I'll give you another example.
So you'll remember when we did range from x to y, it
went y minus 1.
Same kind of thing is happening here.
So that's why s from 0 to one gives me only one character,
but s from 0 to two gives me the character string ab.
This is what's called slicing, and it's very common.
What a slice does, is it makes a new copy--
makes a new object, in this case-- which is a sub-string
of the original string.
There are many other things I can do on strings.
I can do something like s.find, and it will tell me
that b is at position number one in s.
So use Google, whatever you use, to find the Python web
page that describe strings, and it will give you all of
the operations you can do.
And they're quite convenient.
One other scalar type that you're going to need for the
problem set is tuples, and that will be discussed in
recitation tomorrow.
OK, thanks a lot.