411 REFERENCE · Wikibooks
The Science of Programming/EncapsulatingOperations
Reference material by Wikibooks contributors. This retains its source identity and is not 411 news reporting.
Encapsulating Sequences of Operations
Defining Functions
Recall, the series of expressions we evaluated to find
the y-value of a point on the line
given an x-value:
sway> var m = 6;
INTEGER: 6
sway> var x = 9;
INTEGER: 9
sway> var b = -12;
INTEGER: -12
sway> var y = m * x + b;
INTEGER: 42
sway> y;
INTEGER: 42
Now, suppose we wished to find the y-value corresponding to
a different x-value or, worse yet, for a different x-value
on a different line. All the work we did would have to be
repeated. A function is a way to encapsulate all these operations
so we can repeat them with a minimum of effort.
First, we will define a not too useful function that
calculates y give a slope of 6,
a y-intercept of -12, and an
x-value of 9 (exactly
as above).
sway> function findY()
more> {
more> var slope = 6;
more> var x = 9;
more> var intercept = -12;
more> return slope * x + intercept;
more> }
FUNCTION: <function findY()>
Notice that the interpreter prompt changes to more when
it the input is incomplete. In the case of the function definition
above, that occurs when the curly close brace is entered (1).
Once the function is defined, we can find the value of y repeatedly:
sway> findY()
INTEGER: 42
sway> findY()
INTEGER: 42
This function is not too useful in that we cannot use it to compute
similar things, such as the y-value for a different value of
x. A hallmark of a good function is that is lets you compute
more than one thing. We can modify our function to take in the
value of x we are interested in. We do this by passing in
an argument, in this case, the value of x.
sway> function findY(x)
more> {
more> var slope = 6;
more> var intercept = -12;
more> return slope * x + intercept;
more> }
FUNCTION: <function findY()>
We give names to the values being passed in by placing variable
names between the function definition parentheses.
In this case, we chose x as the name.
Notice that since we are passing in x, we no longer need (or want)
the definition of x, so we delete it.
Now we can compute y for an infinite number of x'''s.
sway> findY(9);
INTEGER: 42
sway> findY(0);
INTEGER: -12
sway> findY(-2);
INTEGER: -24
What if we wish to compute a y-value for a given x for a different
line? One approach would be to pass in the slope and interceptas well as x:
sway> function findY(x,slope,intercept)
more> {
more> return slope * x + intercept;
more> }
FUNCTION: <function findY()>
sway> findY(9,6,-12);
INTEGER: 42
sway> findY(0,6,-12);
INTEGER: -12
If we wish to calculate using a different line, we just pass in the
new slope and intercept along with our value of x.
This certainly works as intended, but is not the best way. One problem
is that we keep on having to type in the slope and intercept even if
we are computing y-values on the same line. Anytime you
find yourself doing the same thing over and over, be assured that
someone has thought of a way to avoid that particular tedium.
So assuming that is true, how do we
customize our function so that we only have to enter the slope
and intercept once per particular line? We will explore
three different ways for doing this. In reading further,
it is not important if you understand all that is going on.
What is important is that you know other approaches exist
and understand the pros and cons of each approach:
Creating functions on the fly
At this point, you should see that you can create functions and should
be able to create other functions through pattern matching. For
example, you ought to be able to define a function that squares
a given number:
function square(x)
{
return x * x;
}
Since creating functions is hard work (lots of typing) and
Computer Scientists avoid hard work like the plague, somebody
early on got the idea of writing a function that itself
creates functions! Brilliant! We can do this for our line problem.
We will tell our creative function to create a findY function
for a particular slope and intercept!
function makeFinder(slope,intercept)
{
function findY(x)
{
return slope * x + intercept;
}
return findY;
}
See how our creative function defines a findY function and then
returns it. Now we can create a bunch of different findY's.
sway> var findYa = makeFinder(6,-12);
FUNCTION: <function findY(x)>
sway> var findYb = makeFinder(5,2);
FUNCTION: <function findY(x)>
sway> findYa(9);
INTEGER: 42
sway> findYb(9);
INTEGER: 47
Notice how findYa and findYb remember
the slope and intercept supplied
when they were create.
While this is decidedly cool, the problem is many languages (C and Java
included) do not allow you to define functions that create other functions.
Using objects
Another approach to our line problem is to use something called an object.
In Sway, an object is simply an environment and we have seen those
before. So there is nothing new here except in how to use objects
to achieve our goal. Here, we define a creating function (known as
a constructor) that creates and returns a line object.
function line(slope,intercept)
{
return this;
}
The this variable always points to the current environment, which, in
this case, includes slope and intercept. By returning
this, we return this environment, and we can look up the values ofslope and intercept at our leisure. To prove that slope and intercept
exist, we can use the built-in bindings function:
sway> lineA = line(6,-12);
sway> bindings(lineA);
OBJECT 231:
context : <object 145>
dynamicContext: <object 145>
constructor: <function line(slope,intercept)>
this: <object 231>
intercept: -12
slope : 6
SYMBOL: :true
We access the variables in an object with the '.' (dot) operator:
sway> lineA . slope;
INTEGER: 6
sway> lineA . constructor;
FUNCTION: <function line(slope,intercept)>
Now we modify our findY function to take in a line object as
well as x and use the dot operator to extract the line's slope
and intercept:
function findY(line,x)
{
return line . slope * x + line . intercept;
}
In this scenario, we create different lines, then pass each
line to our new findY function:
sway> var lineA = line(6,-12);
OBJECT: <object 231>
sway> var lineB = line(5,2);
OBJECT: <object 256>
sway> findY(lineA,9);
INTEGER: 42
sway> findY(lineB,9);
INTEGER: 47
The problem with this approach is we have separated line objects from
finding y values, yet these two concepts are closely related. As
an example, suppose we have parabola objects as well as line objects.
Our findY function would fail miserably for parabola objects
even though the concept of (x,y) points on a parabola is just as
valid as points on a line (2).
In the
object oriented world, we solve this problem by
bundling the object and functions that work specifically on
that object together. In our case,
we
make the findY function part of the line object:
function line(slope,intercept)
{
function findY(x)
{
return slope * x + intercept;
}
this;
}
This is very similar to the functions-on-the-fly approach, but we
return this instead of findY. Now we call the findY function
via the line object.
sway> var lineA = line(6,-12);
OBJECT: <object 231>
sway> var lineB = line(5,2);
OBJECT: <object 256>
sway> lineA . findY(9);
INTEGER: 42
sway> lineB . findY(9);
INTEGER: 47
Should we have a parabola object, it would have its own findY function
with a different implementation. We would call it just the same, however:
sway> var parabolaA = parabola(2,0,0);
OBJECT: <object 453>
sway> parabolaA . findY(7);
INTEGER: 49
This approach is supported in object oriented languages such as Java.
The earlier approach (where the function was separated from the object)
is supported in procedural languages such as C.
Questions
Exercises
(1) Typing a long construct into the interpreter is tedious.
Later we will learn how to store our code in a file and have
the interpreter execute the code in that file. If we need to
make changes to our code, we simply edit the file. That
way, we do not need to type in the modified code into the
interpreter from scratch.
(2) Here is a concrete example of trying to generalize'', so that
our function works for all objects for which the concept of
the function is valid.