Lec 4 | MIT 6.00SC Introduction to Computer Science and Programming, Spring 2011


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.

Leave a Reply

Your email address will not be published. Required fields are marked *