file: 02.1.functionsSubstitutionStddraw.txt author: Bob Muller CS1101 Computer Science 1 Lecture Notes for Meeting 1 of Week 2 Topics: 1. Libraries 2. Function Definitions 3. Extending the Simplification Model with Substitution 4. Type Notation for Documenting Function Definitions 5. Using the stddraw Library -------------------------------------------------------------------- 1. Libraries Like most programming languages, Python has vast libraries of code that Python coders can use off-the-shelf in writing their code. For the most part, library code is packaged up in "modules" of functions that are grouped by their purpose. For example, there is a 'math' module with sin and cos functions, a 'random' module for working with random numbers. Python has a Standard Library that comes with all implementations of Python. See the web site: http://www.python.org/doc//current/library/ But the world-wide Python community has developed many, many more, (many thousands) of library modules that provide functionality beyond the standard library. As we discussed, most of these libraries contain code from Python 2.7 rather than the incompatible Python 3.0. Importing Library Modules Functions in the Standard Library modules can be accessed by using the -import- directive. For example, we can make use of the math module's sin function as follows: >>> import math >>> math.sin(3.14) 0.0015926529164868282 So 'math.sin' refers to the sin function as defined in the math module. The notation math.sin(3.14) is an -applyication- of the math.sin function to the argument 3.14. Recall that the sin of pi (the y component) is 0. Since 3.14 is a lousy approximation of pi, the result returned by math.sin is an equally lousy approximation of 0. We can do better: >>> math.sin(math.pi) 1.2246467991473532e-16 Note that this answer is printed in exponential notation, i.e., it is 1.224... x 10^-16 If we don't need or want to specify the module name, we can do the following: >>> from math import sin >>> sin(3.14) Or >>> from math import * # Import all of the names. >>> sin(3.14) To see a list of the functions available in a given module, we can type >>> dir(math) or, for a lot more information: >>> help(math) For now, we're going to focus on one particular Standard Library module, the Built-in Functions. We'll also discuss our own stddraw.py module, which we developed for the purposes of this course. The Built-in Functions are imported for free. You can find them here: https://docs.python.org/2/library/functions.html >>> abs(-3.14) 3.14 >>> round(3.5) 4.0 >>> len('hello') 5 >>> type(3.14) >>> float(3) 3.0 >>> type('hello') >>> str(3.14) '3.14' >>> help(len) Standard Library modules can always be imported without worrying about finding where the modules are stored in your file system. So one can just type: >>> import math and then all of the definitions in the math module are available. Non-standard modules have to be found. Python always looks in the site-packages folder for modules, that's why we put our stddraw.py file in that folder. We're going to make heavy use of both the math and the random modules: >>> import math, random >>> dir(random) ... >>> random.random() 0.22473570201015713 # returns random number r st: 0 <= r < 1.0. >>> Using The stddraw Library >>> from stddraw import * >>> p = Picture() >>> p.filledCircle(.5, .5, .3, "red") >>> p.start() Note that once a picture is "started" the REPL is inaccessible. -------------------------------------------------------------------- 2. Function Definitions 1. Functions are the main tool for packaging up computation in almost all programming languages, 2. Functions have -definitions- and -uses-, 3. A function may or may not return a value. Functions that return no value are usually called -procedures-, 4. Procedures may or may not have explicit -return- statements, 5. Function and procedure definitions are usually stored in a text file with a .py extension. ----------------------------------- 1. A function definition has the form: def functionName(var1, ..., varn): statement ... statement The items var1, ..., varn are -variables-. In this position, the are called -formal parameters-. Variables may also occur in the statements. CONVENTION: function names and variable names start with a lower-case letter and are usually made up of works. For readability, the inner words start with a capitol letter as in: thisIsMyFunctionName or thisIsMyVariableName Or, following Python but no other language, each word is separated with underscores as in: this_is_my_variable_name The sequence of statements is called the -body- of the function. Note that the sequence of statements is indented relative to the line starting with the keyword "def". Unlike most other programming languages, indentation matters in Python. A sequence of statements at the same indentation level is called a -block-. Example: a doubling function def double(x): return x * 2 Example A min3 function that finds the minimum of 3 inputs. def min3(p, q, r): return min(p, min(q, r)) For now, we'll start with four types of statements adding a fifth below. A. the -return- statement, which has the form: return Expression or just: return B. the function call: functionName(Expr1, ..., Expr2) C. the -assignment- statement, which has the form: variable = Expression and D. the -print- statement, which has the form: print Expression, ... We'll describe how these work below. 2. A function -use-, -call- or -application- has the form: functionName(Expr1, ..., Exprn) where each of Expr1, ..., Exprn is an -expression-. -------------------------------------------------------------------- 3. Extending the Simplification Model with Substitution How does a function do its job? Well, it must have a defition and then we can ask, what happens when it is called? 1. When a function call is evaluated, for each i = 1,..,n, Expri is evaluated. This process may produce a value Vi, it may encounter an error or it may not stop (more on that later). If for each i, the evaluation of Expri produces a value Vi, we go to step 2, otherwise we have an error. 2. Each value Vi is plugged-in for vari in the function body (i.e., each occurrence of vari in the body is -replaced- by value Vi). 3. Then the statements making up the function body are executed from top to bottom. So how do the statements work? A. If the statement is a return statement of the form: return Expression The Expression is evaluated. If it produces value V, then the function exits and the call of the function is replaced by V. If the statement is a return statement of the form: return The function exits without producing a value. B. If the statement is a function call: functionName(Expr1, ..., Exprn) the rules for function call (above) are followed. This is a special case wherein the function being called is not expected to return a value. (If it does return one, that value is ignored.) A function that doesn't return a value is often called a -procedure-. C. If the statement is an assigment statement of the form: variable = Expression The Expression is evaluated. If it produces value V, then all subsequent occurrences of the variable are replaced by V. Naming Values We can use the assignment form: variable = Expression to name the value of Expression. FIX THIS >>> 3.14 * 2.0 6.28 >>> PI = 3.14 # NB: Evaluation of this form doesn't # yield a value but it does record # an association between PI and 3.14. >>> PI 3.14 >>> PI * 2.0 CONVENTION: Names of constants are in all caps. D. If the statement is a print statement of the form: print Expression, ... The Expressions are evaluated and their values are printed to the output area. NOTES: 1. The PRINT and RETURN statements behave very differently! Consider: def iReturnDouble(n): return n * 2 def iPrintDouble(n): print n * 2 >>> iReturnDouble(3) 6 >>> iPrintDouble(3) 6 # They seem to behave the same way... >>> iReturnDouble(3) + 1 7 >>> iPrintDouble(3) + 1 7 Error: Cannot add NullType to int ..... Bottom Line: return Exp, returns the value of Exp in a context where that value may be used in a larger expression. 2. For assignment statements, we use var = Expression The "=" is pronounced "gets". We DO NOT USE: Expression = var even though it seems reasonable from the mathematical perspective. 3. If a function body has no return statement, it is as though there is an empty return statement at the end. THIS IS WRONG! def quadruple(z): return double(double(z)) >>> double(2 + 2) = double(4) = return 4 * 2 = return 8 = 8 >>> double(3) + double(2 + 3) = return 3 * 2 + double(2 + 3) = return 6 + double(2 + 3) = 6 + double(2 + 3) = 6 + double(5) = 6 + return 5 * 2 = 6 + return 10 = 6 + 10 = 16 >>> quadruple(4) = return double(double(4)) = return double(return 4 * 2) = return double(return 8) = return double(8) = return return 8 * 2 = return return 16 = return 16 = 16 In Python (as in almost every other programming language!) computations are bundled up in functions! -------------------------------------------------------------------- 4. Type Notation for Documenting Function Definitions Once a function is defined and working, people want to know how to use it. They -could- study its definition to see how it works and how to use it. But this turns out to be a really bad and unworkable idea --- it's a huge waste of time. It would be better to have a concise notation that summarizes how to use the function. It turns out that the -type- of the function plays this role very very well. The type of a function is also called its -signature-. There are a lot of different notations for function types in different programming languages. If you type the following into the Python Shell: >>> help(abs) Python will print: ------------------------ Help on built-in function abs in module __builtin__: abs(...) abs(number) -> number Return the absolute value of the argument. ------------------------ The notation "abs(number) -> number" means that abs is a function that accepts a number as input and returns a number as output. The symbol "->" is a commonly accepted way to denote a function type, but we will follow the more widely accepted convention of writing the type as: abs : number -> number Meaning, abs is a function from numbers to numbers. The authors of the Python documentation use the symbol "number" to refer to -either- an int or a float. This is not a common way to express type information because it lacks precision, we will try to avoid it. It should almost always be the case that we can be more specific. For example, given a definition such as: def successor(n): return n + 1 # a one-liner We would document its type as: successor : int -> int which is pronounced: "successor is a function from ints to ints". Why? Because the expression "n + 1" in the body of the function has an integer 1, so we can infer that the other operand, "n" should be of type int too. Thus, the function accepts an int and returns an int. On the other hand, given the definition: def successor(n): return n + 1.0 We would write: successor : float -> float We will use this more precise notation whenever possible. A second example: def add(m, n): return m + n We might write the signature of add as: add : float * float -> float or add : int * int -> int Here, the asterik symbol "*" is pronounced "cross" (which means "and") so the type would be understood as "add is a function that accepts two floats and returns a float". We will put these types as comments above our function definitions to serve as concise documentation for the human reader. Thus, we will write: # add : float * float -> float # # The call add(m, n) adds m to n and returns the result. # def add(m, n): return m + n NOTE: the special type -void- is the type of nothing. Any function with a return type of void is called a -procedure-. -------------------------------------------------------------------- 5. Using the stddraw Graphics Library The stddraw Library provides a 1.0 x 1.0 unit square together with a number of routines for drawing pictures in the square. The routines are documented in the stddraw API which is available on the Resources page. The stddraw routine -Picture- creates a picture value. Once we've created a Picture we can use the stddraw functions to add shapes to the picture or to alter its properties. NOTE: the picture won't be displayed until the -start- routine is called as in: >>> import stddraw >>> pict = stddraw.Picture() >>> pict.rectangle(.3, .3, .2, .3) >>> pict.filledRectangle(.7, .7, .2, .3, 'green') >>> pict.start() NOTE: There is something fairly subtle happening here. Notice that in the line: pict = stddraw.Picture() the Picture function in the stddraw library is referenced as 'stddraw.Picture'. The Picture function returns a picture value. But in the line that follows: pict.rectangle(.3, .3, .2, .3) the rectangle function in the stddraw library is referenced as pict.rectangle, rather than as stddraw.rectangle. There is a good reason for this but the full explanation of this little puzzle will necessarily have to wait. For now, just know that if you create a picture myPict, you can call the functions in the library using myPict.functionName(Expr1, ..., Exprn) Returning to the code, we'll usually want to package this work up in functions to make it more modular. E.g.: def rectangle(picture, x, y, halfWidth, halfHeight): picture.rectangle(x, y, halfWidth, halfHeight) def filledRectangle(picture, x, y, halfWidth, halfHeight, color): picture.filledRectangle(x, y, halfWidth, halfHeight, color) def testRectangles(): pict = stddraw.Picture() myPict.setW(800) myPict.setH(800) rectangle(pict, .3, .3, .2, .3) filledRectangle(pict, .7, .7, .2, .3, 'green') pict.start() Note that the API documentation for rectangle and filledRectangle specify that the first two arguments, x and y, are understood to be the -center- of the rectangle and that the next two arguments are understood to be half the width and half the height. We may wish to define our own version of square that uses a different reference frame with (x, y) at the lower left and which specifies the length of the side of the square. def rectangle(picture, x, y, width, height): halfWidth = width / 2.0 halfHeight = height / 2.0 picture.rectangle(x + halfWidth, y + halfHeight, halfWidth, halfHeight) And likewise for filledRectangle: def filledRectangle(picture, x, y, width, height, color): halfWidth = width / 2.0 halfHeight = height / 2.0 picture.filledRectangle(x + halfWidth, y + halfHeight, halfWidth, halfHeight, color) Since a square is special case of a rectangle, we can easily define our own versions of square and filledSquare, using the new reference frame: def square(picture, x, y, side): rectangle(picture, x, y, side, side) And likewise for the filledSquare: # filledSquare : Picture * float * float * float * color -> void # def filledSquare(picture, x, y, side, color): filledRectangle(picture, x, y, side, side, color) Thinking about the unit square, a stripe, is also a special case of a filledRectangle: # verticalStripe : Picture * float * float * color -> void # def verticalStripe(picture, x, width, color): filledRectangle(picture, x, 0.0, width, 1.0, color) Responding to Mouse Clicks # randomFilledSquares : void -> void # # The call randomFilledSquares renders a random filled square each # time the mouse is clicked. # import stddraw, random def randomFilledSquares(): def responder(event): x = random.random() y = random.random() side = random.random() color = picture.randomColor() filledSquare(picture, x, y, side, color) picture = stddraw.Picture() picture.bind('', responder) picture.setW(800) picture.setH(800) picture.start()