Fundamentals of Computer Science I: Media Computing (CS151.01 2008S)
Primary: [Front Door] [Syllabus] - [Academic Honesty] [Instructions]
Current: [Outline] [EBoard] [Reading] [Lab] [Assignment]
Groupings: [Assignments] [EBoards] [Examples] [Exams] [Handouts] [Labs] [Outlines] [Projects] [Readings]
References: [A-Z] [Primary] [Scheme Report (R5RS)] [Scheme Reference] [DrScheme Manual]
Related Courses: [CSC151.02 2008S (Davis)] [CSC151 2007F (Rebelsky)] [CSC151 2007S (Rebelsky)] [CSCS151 2005S (Stone)]
In previous labs, you've seen that many algorithms
require more than just one or two procedure calls.
For example, in the lab on turtle
graphics, we saw a series of instructions that created
either a pentagon or a five-side star, depending on the value of
angle
.
(turtle-forward! tommy 50) (turtle-turn! tommy angle) (turtle-forward! tommy 50) (turtle-turn! tommy angle) (turtle-forward! tommy 50) (turtle-turn! tommy angle) (turtle-forward! tommy 50) (turtle-turn! tommy angle) (turtle-forward! tommy 50)
How do we use this code to draw a pentagon? We write an instruction
to set angle
to 72 and follow that instruction with the
instructions to draw the five lines.
(define angle 72) (turtle-forward! tommy 50) (turtle-turn! tommy angle) (turtle-forward! tommy 50) (turtle-turn! tommy angle) (turtle-forward! tommy 50) (turtle-turn! tommy angle) (turtle-forward! tommy 50) (turtle-turn! tommy angle) (turtle-forward! tommy 50)
Clearly, that's a bit cumbersome to write every time we want a pentagon.
What we'd really like to do is to say is that “this group
of code is a procedure that takes an angle as an
input and builds a five-sided figure”.
You know that Scheme has procedures, since
you've used both built-in procedures -- such as
,
and sqrt
-- and DrFu extensions, such as
*
and
turtle-new
. But can you
define your own procedures? Certainly.
image-stroke!
In Scheme, you use define
to give names to procedures, just
as you use it to give names for values. The values just look different.
The general form of a procedure definition is
(define procedure-name (lambda (formal-parameters) body))
The procedure-name part is obvious: It's the name we might give the procedure. The formal-parameters are the names that we give to the inputs. For example, the input to a “draw a shape with five lines” procedure would probably be the angle between consecutive lines. The body is the expression (or sequence of expressions) that do the computation.
For the turtle code above, we might choose the name
. As we just
suggested, one parameter to that procedure will be the angle between
consecutive lines. Do we need any others? Yes! We also need
to specify which turtle should draw the lines. By convention, the
turtle will be the first parameter, which means that the angle should
be the second parameter. Do we need others? Well, it may be the
case that not every five-sided figure should have a side length of 50,
so we'd make that a parameter, too. What should the body look like?
A lot like the code above.
turtle-penta-draw!
Putting it all together, we get
(define turtle-penta-draw! (lambda (turtle angle side) (turtle-forward! turtle side) (turtle-turn! turtle angle) (turtle-forward! turtle side) (turtle-turn! turtle angle) (turtle-forward! turtle side) (turtle-turn! turtle angle) (turtle-forward! turtle side) (turtle-turn! turtle angle) (turtle-forward! turtle side)))
This brand new procedure can now be called as it were a built-in
procedure. Hence, to have a turtle named tommy
draw a
pentagon with each edge of length 80, we might write
>
(turtle-penta-draw! tommy 72 80)
The
procedure
is called for its side effect of drawing on
the screen. (We end the name with an exclamation point to reminder
ourselves that it has a side effect.) However, just as often, we
write procedures that return newly computed
values. And we can write procedures that can compute anything we
know how to write an expression for. For example, here is a simple
turtle-penta-draw!
procedure.
square
(define square (lambda (n) (* n n)))
We can (and should) test the procedure.
>
(square 2)
4
>
(square -4)
16
>
(square square)
Error: *: argument 1 must be: number.
The square computation is fairly simple. We can, of course, write more complex expressions. For example, consider the problem of generating an average numeric grade, given a six grades (on exams, quizzes, whatever). A pure average would simply add the six numbers together and divide by six.
(define compute-grade (lambda (grade1 grade2 grade3 grade4 grade5 grade6) (/ (+ grade1 grade2 grade3 grade4 grade5 grade6 ) 6)))
A more generous policy for dealing with such grades would be to drop the lowest grade and count the highest grade twice. We might express that policy as
(define compute-grade (lambda (grade1 grade2 grade3 grade4 grade5 grade6) (/ (+ grade1 grade2 grade3 grade4 grade5 grade6 (max grade1 grade2 grade3 grade4 grade5 grade6) (- (min grade1 grade2 grade3 grade4 grade5 grade6))) 6)))
Of course, it would be nice to make this procedure work with a varying number of homework grades. We'll see one such strategy in a few days, when we begin to explore lists.
Let's look at another example to think a bit more about how one creates a parameterized procedures. Often, we start with code that accomplishes a particular task and think about how (and why) we might generalize it. As an example, consider the following variation of instructions in the lab on drawings as values. These instruction generating a green circle of diameter 40, centered at (60,100).
(define sample-circle (drawing-recolor (drawing-hshift (drawing-vshift (drawing-scale drawing-unit-circle 40) 100) 60) "green"))
What would we change if we wanted the circle to be red?
We'd replace the "green"
with "red"
. What
would we change if we wanted the radius to be 100? We'd replace
the 40
by 200
. Similarly, we would change
the 60 and 100 for a different origin. We might, therefore, generalize
the expression by replacing the constant values with variables, as
in
(define general-circle (drawing-recolor (drawing-hshift (drawing-vshift (drawing-scale drawing-unit-circle (* 2 radius)) center-row) center-col) color))
We could now draw the same circle by defining edge-size
,
center-row
, center-col
, and color
.
(define radius 20) (define center-col 60) (define center-row 100) (define color "green") (define general-circle (drawing-recolor (drawing-hshift (drawing-vshift (drawing-scale drawing-unit-circle (* 2 radius)) center-row) center-col) color))
While this code is a little bit longer than the original, it's much clearer. When we want to draw something different, we can just change one or more of the definitions. And that's just what we would have done before learning about procedures. But we can now encapsulate the code into a procedure.
(define drawing-new-circle (lambda (color radius center-row center-col) (drawing-recolor (drawing-hshift (drawing-vshift (drawing-scale drawing-unit-circle (* 2 radius)) center-row) center-col) color)))
Convention in Scheme (and all programming languages) is that we carefully document what our procedures do, including input values, output values, and assumptions. We use comments provide information to the reader of our program (that is, to people instead of the computer). In Scheme, comments begin with a semicolon and end with the end of the line.
There are a variety of kinds of comments we write. For now, we'll focus on the comments we write for the other programmers who might call the procedures we write.
;;; Samuel A. Rebelsky and Janet Davis ;;; Department of Computer Science ;;; Grinnell College ;;; {rebelsky,davisjan}@grinnell.edu ;;; Procedure: ;;; square ;;; Parameters: ;;; val, a number ;;; Purpose: ;;; Compute val*val ;;; Produces: ;;; result, a number ;;; Preconditions: ;;; val must be a number ;;; Postconditions: ;;; result is the same "type" of number as val (e.g., if ;;; val is an integer, so is result; if val is exact, ;;; so is result). ;;; Citations: ;;; Based on code created by John David Stone dated March 17, 2000 ;;; and contained in the Web page ;;; http://www.math.grin.edu/~stone/courses/scheme/procedure-definitions.xhtml ;;; Changes to ;;; Parameter names ;;; Formatting ;;; Comments (define square (lambda (value) (* value value)))
Yes, that's a lot of documentation for very little code. However, it is better to err on the side of too much documentation than too little documentation. More importantly, as you start writing more procedures, their purpose and details will be much less obvious when you come back to them. Finally, when you carefully document procedures, you begin to think more carefully about what they really need to do and how you ensure that they do so for all cases.
Consider again the
procedures from above. We can write a common set of documentation for
them:
compute-grade
;;; Procedure: ;;; compute-grades ;;; Parameters: ;;; grade1, a real number ;;; grade2, a real number ;;; grade3, a real number ;;; grade4, a real number ;;; grade5, a real number ;;; grade6, a real number ;;; Purpose: ;;; Compute a weighted average of the six grades, using the ;;; top-secret course grading policy. ;;; Produces: ;;; grade, a number ;;; Preconditions: ;;; All grades must be non-negative. ;;; Postconditions: ;;; grade is no smaller than the smallest of the six grades. ;;; grade is no larger than the largest of the six grades. ;;; grade is non-negative (implied by previous postconditions).
It turns out that writing this documentation helped us think a bit more about some particular issues that relate to the procedure. For example, we needed to specify something about the result that the client would find useful. At the same time, we wanted to keep the documentation general enough that we could use either policy. That made us decide that putting limits on the result was appropriate. Those limits are, however limiting. We cannot, for example, write a procedure that gives as student who does every assignment a bit of extra credit, since that might lead to a grade higherthan any given so far.
We also had to specify that the procedure took numbers as inputs and produced a number. Without such consideration, we might have had an awkward moment in which someone called our procedure with letter grades, or expected it to return a letter grade.
Primary: [Front Door] [Syllabus] - [Academic Honesty] [Instructions]
Current: [Outline] [EBoard] [Reading] [Lab] [Assignment]
Groupings: [Assignments] [EBoards] [Examples] [Exams] [Handouts] [Labs] [Outlines] [Projects] [Readings]
References: [A-Z] [Primary] [Scheme Report (R5RS)] [Scheme Reference] [DrScheme Manual]
Related Courses: [CSC151.02 2008S (Davis)] [CSC151 2007F (Rebelsky)] [CSC151 2007S (Rebelsky)] [CSCS151 2005S (Stone)]
Copyright (c) 2007-8 Janet Davis, Matthew Kluber, and Samuel A. Rebelsky. (Selected materials copyright by John David Stone and Henry Walker and used by permission.)
This material is based upon work partially supported by the National Science Foundation under Grant No. CCLI-0633090. Any opinions, findings, and conclusions or recommendations expressed in this material are those of the author(s) and do not necessarily reflect the views of the National Science Foundation.
This work is licensed under a Creative Commons
Attribution-NonCommercial 2.5 License. To view a copy of this
license, visit http://creativecommons.org/licenses/by-nc/2.5/
or send a letter to Creative Commons, 543 Howard Street, 5th Floor,
San Francisco, California, 94105, USA.