A Balanced Introduction to Computer Science and Programming

David Reed
Creighton University

Copyright © 2004 by Prentice Hall



Chapter 7: Abstraction and Functions


Abstraction is the act of ignoring the minutiae and focusing on the big picture. We are constantly confronted with complex objects in life, objects much too complex to be understood by the average person. For example, to really understand how a television works, one must have extensive knowledge in electrical engineering, electronics, and physics. And yet, we all watch and operate television sets everyday. We do so by abstracting away unnecessary details and focusing on the features that are relevant to our needs. At the level of abstraction that most people view a television set, it is a box with several inputs (power cord, on/off button, channel selector, volume control) and two outputs (picture and sound). This level of understanding suffices to allow us to watch and have control over the television set.

You have already been introduced to the idea of abstraction in programming through JavaScript's predefined functions. Functions in a programming language represent units of computation whose details are abstracted away. For example, the computation involved in finding the square root of a number is certainly non-trivial, and yet the details of this process are abstracted away by the Math.sqrt function. Without worrying about how this function computes the square root, the programmer is able to call this function and use it effectively.

In this chapter, you will learn how to introduce your own abstractions into JavaScript by defining your own functions.


User-defined functions

Functions simplify the programmer's task in two major ways. First, functions help to minimize the amount of detail that the programmer must keep track of. Since the Math.sqrt function is available, the programmer does not need to remember the sequence of steps involved in computing the square root. She only needs to remember how to call the Math.sqrt function. Second, functions help to minimize the size and complexity of code. Once the Math.sqrt function has been defined, a single call to this function will suffice to compute a square root. Since the number of steps required to otherwise compute the square root would be considerable, this can represent a significant savings in the complexity of code.

JavaScript's predefined functions represent a collection of useful, general purpose abstractions. Since computations such as the square root and absolute value are common in many applications, the designers of JavaScript provided these abstractions for you in the form of predefined functions. In addition to these, you are able to define new abstractions by defining functions of your own design. Any computation that you find especially useful can be encapsulated as a function. Once defined and loaded into a Web page, your function can be called just like any predefined function, and so may be viewed as extending the capabilities of the JavaScript language.

As an example, recall the formula for converting a temperature in degrees Fahrenheit to degrees Celsius:

(5/9) * (tempInFahr - 32) In Chapter 5, you evaluated this expression for various values of tempInFahr. If converting temperatures was a task that you were to perform relatively often, you would spend a lot of time remembering and retyping this formula. Alternatively, you could encapsulate this computation in a function and save it. Any time you wanted to convert temperatures, you could then load this function and call it. You wouldn't need to remember the formula anymore, just the name of the function and how to call it.

The following is the definition of a JavaScript function that performs the temperature conversion. A description of the key parts of this definition are given below:

function FahrToCelsius(tempInFahr) // Assumes: tempInFahr is a temperature in Fahrenheit // Returns: the equivalent temperature in Celsius { return (5/9) * (tempInFahr - 32); }

In order to make a user-defined function accessible in a Web page, it must be included in that page. The simplest way to do this is to include its definition directly in the HEAD of the page, enclosed in SCRIPT tags. For example, the following page defines the FahrToCelsius function in the HEAD, then calls it from the BODY to convert a temperature from Fahrenheit to Celsius. In fact, this page is equivalent to the ftoc.html page you wrote in Chapter 5.

<html> <!-- convert.html Dave Reed --> <!-----------------<script type="text/javascript">------------------------------> <head> <title> New Improved Temperature Conversion </title> <script type="text/javascript"> function FahrToCelsius(tempInFahr) // Assumes: tempInFahr is a temperature in Fahrenheit // Returns: the equivalent temperature in Celsius { return (5/9) * (tempInFahr - 32); } </script> </head> <body> <script type="text/javascript"> tempInFahr = prompt("Enter a temperature (in Fahrenheit)", "32"); tempInFahr = parseFloat(tempInFahr); document.write("You entered " + tempInFahr + " degrees Fahrenheit.<br />"); tempInCelsius = FahrToCelsius(tempInFahr); document.write("That's equivalent to " + tempInCelsius + " degrees Celsius."); </script> </body> </html>


EXERCISE 7.1:    Cut-and-paste the convert.html text into a new Web page and load this page into the browser to verify that it behaves as before.

Once you have done this, modify the page so that it prompts the user for two temperatures, the low and high temperatures for the day. The page should display both temperatures as they were entered, as well as their Celsius equivalents.


EXERCISE 7.2:    Define a function named CelsiusToFahr for converting Celsius temperatures to Fahrenheit. The conversion from Celsius to Fahrenheit is as follows: tempInFahr = ((9/5) * tempInCelsius) + 32; Add the definition of your CelsiusToFahr function to the HEAD of the convert.html page. Note that multiple function definitions can be placed in the HEAD within the same pair of SCRIPT tags. Modify the body of the page so that it also reads a temperature in degrees Celsius and then displays that temperature in Fahrenheit.


Common errors to avoid...

The rules for valid function names are the same as for variable names: the name must begin with a letter and consist of letters, digits, and underscores. In fact, there is nothing to stop you from treating a function name as a variable, assigning it a new value. For example, in the convert.html page above, you might have inadvertently reused the name FahrToCelsius for a variable in the body of the page. This would not cause an error in itself, but any subsequent attempt to call the function would result in an error with the message "Error: FahrToCelsius is not a function." As with any variables, take care to select unique names to avoid conflicts.



Functions with no return values

Recall that, mathematically speaking, a function is a mapping from some number of inputs to an output. It might seems strange, then, to consider functions that do not return any value. However, you have already seen one example of such a function: document.write. The write function takes a message (i.e., a string) as input and displays that message in the Web page. While it does not technically return a value, you may think of the displayed message as the output of the function.

For tasks that involve repetitive output, functions can be defined that encapsulate sequences of write statements. For example, in Chapter 4 you wrote a Web page that displayed verses of the children's song Old MacDonald. In order to display multiple verses, you were forced to cut-and-paste copies of the write statements for displaying a single verse. To display a version of the song consisting of a cow verse, a pig verse, a duck verse, and a horse verse, you would need to make four copies of the write statements and modify the animal and sound for each (or else use variables whose values are changed between the verses). Alternatively, you could define a single function that displays a verse and then call that function repeatedly.

<html> <!-- newmac.html Dave Reed --> <!-----------------------------------------------> <head> <title> New Improved Old MacDonald </title> <script type="text/javascript"> function OldMacVerse(animal, sound) // Assumes: animal and sound are strings // Results: displays corresponding Old MacDonald verse { document.write("<P>Old MacDonald had a farm, E-I-E-I-O.<br />"); document.write("And on that farm he had a " + animal + ", E-I-E-I-O.<br />"); document.write("With a " + sound + "-" + sound + " here, and a " + sound + "-" + sound + " there,<br />"); document.write("&nbsp;&nbsp; here a " + sound + ", there a " + sound + ", everywhere a " + sound + "-" + sound + ".<br />"); document.write("Old MacDonald had a farm, E-I-E-I-O.</p>"); } </script> </head> <body> <script type="text/javascript"> OldMacVerse("cow", "moo"); OldMacVerse("pig", "oink"); OldMacVerse("duck", "quack"); OldMacVerse("horse", "neigh"); </script> </body> </html>

Note that the OldMacVerse function does not contain a return statement. The purpose of a call to OldMacVerse is to display the verse in the page, not return a computed value. As such, the function calls are treated as statements that appear on lines by themselves, similar to write statements.


EXERCISE 7.3:    Cut-and-paste the newmac.html text into a new Web page and load this page in the browser to verify that it displays the four verses as described. Add code to the page so that it prints a fifth verse (use your favorite animal).

When a function has multiple inputs, the order in which they are specified in a function call is important. In particular, the first input in the function call is assigned to the first parameter in the function, the second input is assigned to the second parameter, and so on. For example, for the call OldMacVerse("cow", "moo"), the input value "cow" is assigned to the parameter animal while "moo" is assigned to sound.


EXERCISE 7.4:    What would you expect to happen as a result of the call OldMacVerse("baa", "sheep")

Verify your prediction in your newmac.html page.




Designer secrets...

Since functions merely encapsulate other statements under a single name, their addition to our repertoire of programming tools does not allow us to solve new types of problems. We are currently limited to solving problems that involve reading and storing values (using prompts and assignments), performing computations on those values (using expressions and Math functions), and displaying the results (using write statements). However, the ability to define and use functions is instrumental in solving complex problems, and also in developing code that can be reused. As the Old MacDonald example demonstrated, defining a function and calling it repeatedly can greatly simplify and shorten code. Likewise, being able to define functions and place them in libraries, such as random.js, means that previously written code can be reused easily.

Defining a function requires recognizing what the inputs are, and what the resulting computation on those inputs should produce. For example, when converting a temperature form Fahrenheit to Celsius, there is one value that needs to be known (the Fahrenheit temperature) and the result of the computation is the corresponding temperature in Celsius. Thus, the function for converting a temperature has one input and returns the value defined by the conversion formula.

For beginners, knowing how to define a function is generally not as difficult as knowing when to define a function. For now, the following design guidelines apply:

  • If a particular computation is complex, requiring extra variables and/or multiple lines to define, place the code for that computation in a separate function.
  • If a particular computation is to be performed repeatedly within the page, then place the code for that computation in a separate function and simply call that function repeatedly as needed.
  • If a particular computation is likely to be useful in many different pages, then place the code for that computation in a function in a separate library file, and load that library as needed.



Parameters and local variables

Functions may be viewed as units of computational abstraction, since they encapsulate a computation and allow the user to perform that computation over and over simply by calling the function. Parameters serve an important role since they allow for generalized computations that may vary over some range of values. For example, the FahrToCelsius function isn't just limited to converting a single temperature from Fahrenheit to Celsius, it works for any temperature you provide as input. Similarly, the OldMacVerse function can be used to print any verse of the song, simply by providing the desired animal name and sound as inputs.

Technically, a parameter is a variable that belongs to the function. When a function is called, memory cells are allocated for the parameters just like any other variable. Unlike normal variables, however, parameters are automatically assigned values when the function is called: the parameters are assigned whatever input values are provided in the function call. Thus, the call FahrToCelsius(100) has the result of assigning the value 100 to the parameter tempInFahr. Likewise, the call OldMacVerse("duck", "quack") assigns "duck" and "quack" to animal and sound, respectively. Once the parameter has been assigned a value, it can be referred to within the function just like any other variable. When the function terminates, the parameters "go away" and their associated memory cells associated are freed.

One useful point to note is that a function defines a new environment where variables can occur. In particular, it is legal to have a parameter with the same name as a variable in the BODY of the page. For example,

<html> <!-- testmac.html Dave Reed --> <!------------------------------------------------> <head> <title> Old MacDonald Test</title> <script type="text/javascript"> function OldMacVerse(animal, sound) // Assumes: animal and sound are strings // Results: displays corresponding Old MacDonald verse { document.write("<P>Old MacDonald had a farm, E-I-E-I-O.<br />"); document.write("And on that farm he had a " + animal + ", E-I-E-I-O.<br />"); document.write("With a " + sound + "-" + sound + " here, and a " + sound + "-" + sound + " there,<br />"); document.write("&nbsp;&nbsp; here a " + sound + ", there a " + sound + ", everywhere a " + sound + "-" + sound + ".<br />"); document.write("Old MacDonald had a farm, E-I-E-I-O.</p>"); } </script> </head> <body> <script type="text/javascript"> animal = prompt("Enter the name of an animal:", ""); sound = prompt("What sound does it make?", ""); OldMacVerse(animal, sound); OldMacVerse("duck", "quack"); </script> </body> </html>

In this page, the variable names animal and sound appear in two different places: in the BODY and also as a parameters in the OldMacVerse function. To the browser, these are completely different sets of variables since they appear in different environments (much the same way that Paris, France and Paris, Texas are recognizable as different cities). If it helps, you might think of each variable with a subscript, identifying its environment: animalBODY, soundBODY, animalOldMacVerse, and soundOldMacVerse. When OldMacVerse is called the first time, the corresponding variables in the BODY and in the function will have the same value (since animalBODY and animalOldMacVerse are passed in as inputs). For the second call, however, the parameters will be assigned "duck" and "quack", independent of the variables in the BODY. This distinction is important since it means that your choice of parameter names when you define a function is not constrained by the variables that already exist. You can choose whatever name you wish without fear that conflicts will occur.


EXERCISE 7.5:    Cut-and-paste the testmac.html text into a new Web page and load this page in the browser to verify that it behaves as described. If you reversed the inputs in the first function call, i.e., OldMacVerse(sound, animal), would the browser be smart enough to recognize the mismatch? Why or why not?



In addition to parameters, it is perfectly legal to have additional variables within a function. In previous chapters, we have seen that variables are useful for storing intermediate steps in a complex computation or for localizing changes. For example, consider the following tax system:

The following function encapsulates this computation, using variables to store intermediate values.

function incomeTax(income, itemized) // Assumes: income >= 0, itemized >= 0 // Returns: flat tax (13%) due after deductions { var deduction, taxableIncome, totalTax; deduction = Math.max(itemized, 4150); taxableIncome = Math.max(income - deduction, 0); totalTax = 0.13*taxableIncome return totalTax; } The first line in this function is a variable declaration specifying that the new variables deduction, taxableIncome, and totalTax are to be used in the function. Declaring variables in this way signifies that they are local to the function, meaning they will only exist inside of the function. In this sense, local variables are similar to parameters. Declaring a local variable tells JavaScript that you are going to use it as temporary storage while computing the function value, and that the storage should go away when the function is done.


EXERCISE 7.6:    Create a Web page named taxes.html and cut-and-paste the incomeTax function into the HEAD (enclosed in SCRIPT tags). In the BODY of the page, prompt the user for their income and itemized deduction, and display the amount of tax owed.

Use your page to determine the amount a person would owe with:

income = 100000.00 itemized = 12017.50 income = 42500.00 itemized = 8900.99 income = 13267.45 itemized = 14000.00

In general, whenever new variables are to be used in a function, those variables should be declared, preceded by var, at the beginning of the function. Otherwise, the variables would persist after the function call was completed, and might accidentally overwrite existing variables. Thus, the general form for function definitions is as follows:

function FUNCTION_NAME(PARAMETER_1, PARAMETER_2,..., PARAMETER_n) // Assumes: DESCRIPTION OF ASSUMPTIONS MADE ABOUT PARAMETERS // Returns: DESCRIPTION OF VALUE RETURNED BY FUNCTION { var LOCAL_1, LOCAL_2,..., LOCAL_m; STATEMENTS return EXPRESSION_SPECIFYING_FUNCTION_VALUE; }

where the function name, parameters, local variables, statements, and function value will differ depending on the task at hand. The return statement is optional, since some functions such as OldMacVerse are called in order to display values on the page as opposed to compute a return value.


Common errors to avoid...

Declaring variables within a function using VAR specifies that those variables are to be treated as local to the function, existing only while the function executes and independent of other variables in the page. Forgetting to declare a local variable will not cause an error, but may lead to confusion if variables with the same name occur elsewhere. For this reason, it is STRONGLY recommended that you double check each function as you write it, making sure that any variables used to store temporary values are declared.

Since parameters are handled automatically by the function, they do not need to be declared. In fact, some browser versions will report an error if you accidentally declare a parameter.



EXERCISE 7.7:    Computer science often deals with extremes when it comes to time. Modern computers can perform billions of operations per second, and yet some tasks such as decoding an encrypted message still might require decades of computation.

Create a Web page named years.html that will prompt the user for some number of years and display the corresponding time in seconds. To simplify this task, ignore leap years and assume that all years consist of exactly 365 days. For example, given an input value of 2.5, your page might display the following:

You entered 2.5 years. That's 78840000 seconds!

Your page should include the definition of a function named yearsToSeconds that has one input, corresponding to a number of years, and returns the corresponding time in seconds. Feel free to use local variables to store temporary values in the computation. For example, you might first convert the number of years to days and store that value in a variable called numDays, then convert that value to hours, minutes, and finally seconds. Be sure to declare any variables that you use within the function.

Once you have your page working, use it to approximate how many seconds are left in this semester. In your college career? In your life? Report your results.



The random.js library

All of the functions we have written so far have been fairly specialized. For example, it is difficult to imagine other applications in which the OldMacVerse function would be useful. In such a case, placing a function definition directly in the HEAD of the page that uses it is a natural approach.

For more general-purpose functions, ones that might be used in a variety of applications, a more general approach is needed. Instead of placing their definitions directly in Web pages, general-purpose functions can be defined in separate files, or libraries, and then loaded into pages as needed. A library file is loaded into a page using SCRIPT tags with the SRC attribute assigned to the file name:

<script type="text/javascript" src="LIBRARY_FILENAME"></script>

Note that there is no code between the two script tags -- the code is contained in the library file and inserted into the page via the tags.

As an example, the library file random.js has been provided for your use (accessible at http://www.creighton.edu/~csc107/random.js). This library contains several general-purpose functions that can be called to generate random values. Recall that the Math.random function can be called to generate a random number in the range [0...1). The functions in the random.js library build upon this function to provide more flexibility and power. In particular, the following functions are defined:

FUNCTION INPUTS DESCRIPTION



RandomNum 2 numbers (low and high limits of a range)
   e.g., RandomNum(2, 4.5)
returns a random number from the range [low...high)
RandomInt 2 integers (low and high limits of a range)
   e.g., RandomInt(1, 10)
returns a random integer from the range [low...high]
RandomChar 1 non-empty string
   e.g., RandomChar("abcd")
returns a random character taken from the string
RandomOneOf 1 list of options (in braces, separated by commas)
   e.g., RandomOneOf(["yes", "no"])
returns a random value taken from the list of options

In order to make use of any of these functions, a Web page simply needs to include the library file in the HEAD using SCRIPT tags. For example, the following page loads the random.js library and displays a number generated by the RandomInt function.

<html> <!-- pickem.html Dave Reed --> <!------------------------------------------------> <head> <title> Random Number Test</title> <script type="text/javascript" src="http://www.creighton.edu/~csc107/random.js"> </script> </head> <body> <script type="text/javascript"> x = RandomInt(1, 3); document.write("The number is " + x + "."); </script> </body> </html>


Common errors to avoid...

When attempting to load a library of JavaScript code, there are two common errors that occur.

  1. If the tags are malformed or the name/address of the library is incorrect, the library will fail to load. This will not cause an error in itself, but any subsequent attempt to call a function from the library will produce "Error: Object Expected" (using Internet Explorer) or "Error: XXX is not a function" (using Netscape).
  2. If a pair of SCRIPT tags are used to load a library of code (using the SRC attribute), then that is all they can do. In particular, you cannot put additional JavaScript code between the tags. You may think of the SRC attribute as causing the contents of the library to be inserted between the tags, overwriting any other code that was erroneously placed there. Thus, if you wish to load a library and also define a function of your own in the HEAD, two pairs of SCRIPT tags would be required.



EXERCISE 7.8:    The traditional test for determining whether someone has ESP (Extra-Sensory Perception) involves having them guess randomly selected values. For example, you might generate random integers in some range, say 1 to 3, and then have the person try to guess each number. Given three possible values, you would expect random guessing to be correct roughly one third of the time. If a person were correct significantly more often, you might be tempted to say that they have ESP (or, more scientifically, that they were just lucky this time).

Cut and paste the pickem.html text into a new Web page and modify the page so that it serves as an ESP test. Your code should generate a random number, either 1, 2 or 3, store that number in a variable, and then prompt the user for a guess. After they enter their guess, your page should display that guess along with the random number that was generated. For example,

You guessed 2. My number was 3.

Once your page is complete, test your ESP potential by repeatedly loading the page and trying to guess the number. Out of 30 tries, how many times did you guess correctly? Do you think you have ESP?




EXERCISE 7.9:    An oracle is a person or entity that is able to reveal hidden knowledge or divine purpose. The Oracle at Delphi, for example, was a shrine where the ancient Greeks would go to ask the gods for guidance and advice. The modern day equivalent of the Oracle at Delphi is the Magic 8-ball (Tyco Toys, Inc.). The Magic 8-ball is an oversized billiard ball with a small window on its side. To receive its wisdom, you ask the Magic 8-ball a question, shake it vigorously, and then look at the response that appears in the window. Functionally speaking, the Magic 8-ball determines its answer by randomly selecting from a list of predetermined responses, such as "yes", "no", and "unclear - try again".

Create a Web page named magic.html that simulates a magic 8-ball. It should prompt the user for a question, then use the RandomOneOf function to randomly select from a list of possible responses. Both the question and response should be displayed on the page. For example,

QUESTION: Will the Cubs win the world series in my lifetime? ANSWER: highly unlikely

Test your page to make sure that it does indeed provide different answers each time it is loaded.



Programming errors and debugging

In computer jargon, the term bug refers to an error in a program. Although the origin of the term is a topic of some debate, it goes back at least as far as 1945 to the Harvard Mark II project, where an error was eventually traced to a moth which had flown into the computer and shorted out one of its relays. In any case, debugging is the process of systematically locating and fixing errors in a program.

Debugging is not fun. However, it is a fact of life for programmers. It is very rare that a program (at least one of nontrivial size) works perfectly the first time. Instead, programming is a continual cycle of design, test, debug, redesign, test, debug, and so on. Errors that you find when testing and debugging your program may lead to finding more errors, and can force the redesign of entire sections of code.

There are three types of errors that can occur in a program.

  1. Syntax errors are simply typographic errors, e.g., forgetting a semi-colon or calling a function with the wrong number of arguments. These types of errors are caught by the browser and so are usually easy to identify and fix.
  2. Run-time errors are errors that occur when operations are applied to illegal values, such as division by zero or attempting to multiply a string. These errors will also be caught by the browser, either producing an error message or, in the case of number values, continuing execution with the NaN value.
  3. Logic errors represent flaws in the design or implementation of the program. Any time you type in a program and it runs but gives the wrong answer, the cause is a logic error (or, as is often the case, numerous logic errors). Unlike syntax and run-time errors, logic errors are not caught by the browser. The code is legal - it's just not what you intended! It is up to you to find where the program went wrong.

A simple but effective way of identifying logic errors in code is to add diagnostic write statements at various places. These are nothing more than write statements that display the values of variables throughout the execution. If, for example, you see that a certain variable has the correct value at one point in the code, but an incorrect value at a later point, then you know that an error occurred somewhere in between. When debugging a function, it is often useful to place a diagnostic write statement at the beginning to display the values of the parameters to ensure they are correct. Once you have identified and fixed the logic errors in your code, you can remove the diagnostic write statements.

An obvious remark to make at this point is that it is much better to avoid bugs than to have to fix them. This is a profound lesson to be learned, though. A little extra time spent going over the design of your program at the start can save you lots of debugging time later on. As a rule, students tend to rush into coding without thinking through their ideas, and then become frustrated when they spend a majority of their time trying to debug ill-conceived code. As Henry Ledgard once quipped, "The sooner you start coding your program, the longer it's going to take."

Another lesson to be learned is that bugs are rarely novel. As you program, you will likely make the same mistakes over and over until you learn from them. Be sure to approach debugging as an investment in time. When you identify a bug, make a mental note of the error and how it manifested itself, so that similar bugs can more easily be identified in the future.



Lesson Summary


Supplemental (optional) material and exercises Solutions to odd numbered exercises