Defining Your Own Scheme Procedures
Summary: We explore why and how you might define your own
procedures in Scheme.
Drawings, Revisited
As you may recall from a recent reading, we designed a simple
drawing data type that we can use to describe
simple images. We can also render those drawings on GIMP images.
Here are a few simple shapes.
Here's what they look like when rendered.
black-circle
purple-ellipse
blue-i
red-eye
A Problem: Repeated Computations
In these examples, as well as in some previous readings and labs, you
have seen that many algorithms require more than just one or two
procedure calls. For example, to build a black circle of radius 25,
centered at 40, 30, we need to
Scale the unit circle by 50.
Horizontally shift that circle 40 units to the right.
Vertically shift that circle 30 units down.
We've just seen code to do just that.
More generally, given a radius, an x coordinate, and a y coordinate, we
might write
(define radius 25)
(define x-center 45)
(define y-center 35)
(define my-circle
(drawing-hshift
(drawing-vshift
(drawing-scale
drawing-unit-circle
(* 2 radius))
y-center)
x-center))
Clearly, that's a bit cumbersome to write every time we want a circle.
What we'd really like to do is to say is that this group
of code is a procedure that takes as input a
radius, the x coordinate of the center, and the y coordinate of the
center
.
You know that Scheme has procedures, since
you've used both built-in procedures -- such as
sqrt,
and * -- and MediaScheme extensions, such as
drawing-hshift and
drawing->image. But can you
define your own procedures? Certainly. How and why
you do so are the primary topics of this reading.
A Simpler Problem: Varying Drawings
Before we progress to our own circle
procedure (which we've just seen takes three parameters), let's consider
a simpler procedure, one that takes just one parameter. In particular,
we will consider a procedure that makes a variant of a drawing that is
scaled up by 25%, shifted right 20 pixels, and then down another 15.
(Why would we want such a variant? Here's one reason: If we recolor
the variant, it could serve as a shadow
for the original drawing.
However, for now, we just use it as an interesting alternative to the first
drawing.)
For any of our drawings above, the code is straightforward. For example,
black-circle-1
purple-ellipse-1
In fact, we might even combine an image with its variant.
(drawing-group black-circle black-circle-1)
(drawing-group purple-ellipse purple-ellipse-1)
However, we've written very similar code to define
black-circle-1 and purple-ellipse-1. We
would certainly benefit from combining the code into a single procedure.
Defining Procedures
So, how do you define procedures?
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.
Procedure to Vary Images
For the two procedures mentioned above, we might choose the names
variant-1 (build a variant of
a drawing) and variant-pair-1
(pair a drawing with its variant). In each case, the procedure will
take one parameter, a drawing.
In essence, to define the body of the first
procedure, all we need to do is to call our parameter
drawing and use the code from
above, with drawing in place of
black-circle or purple-oval.
This brand new procedure can now be called as it were a built-in
procedure. For example,
(variant-1 blue-i)
(variant-1 red-eye)
Pairing an image with its variant is even easier. We just build an
image group of the image and its variant.
(variant-pair-1 blue-i)
(variant-pair-1 red-eye)
Adding Neighbors
One potential deficiency of variant-1
(and, therefore, with pair-variant-1)
is that it always uses the same horizontal and vertical offset.
Arguably, we might want to make the offset depend on the size of the
original drawing. Fortunately, we can obtain some information on
drawings. In particular,
(drawing-width drawing)
gives the width of a drawing and
(drawing-height drawing)
gives its height.
To make a right neighbor to a drawing, we could simply make a copy of the
drawing that is is offset horizontally the width of the first drawing.
(add-right-neighbor purple-ellipse)
(add-right-neighbor blue-i)
Computing with Non-Images
While the procedure above is intended to build a drawing, we certainly
write procedures to deal with other kinds of values.
In particular, we can write procedures that can compute anything we
know how to write an expression for. Often, we write procedures to
help us with mathematical computation.
For example, here is a simple
square procedure
that computes the square of a number.
(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.
We will consider more such procedures when we examing Scheme's numeric
values in more depth.
Making Shapes
Let's return to the earlier example: How do we build a circle of a
particular radius, centered at a particular point? Let's look at
the general
code we wrote earlier.
(define my-circle
(drawing-hshift
(drawing-vshift
(drawing-scale
drawing-unit-circle
(* 2 radius))
y-center)
x-center))
What do we need to change to turn this definition of a single
circle into a procedure? Not much. We just choose a name
(e.g., circle), add a
lambda to make it a function, choose the
parameters (radius,
x-center, and
y-center), and add the generalized
code from above.
(circle 10 5 5)
(circle 50 100 120)
What would we do if we wanted to color our circles? We could add another
parameter to the procedure to represent the color. What would we do to draw a rectangle, rather
than a circle? We'd probably replace
radius with both
width and
height. You will have the
opportunity to try developing procedures like these in the lab.