9. Modules

Last revision: February 9 1998

In this section we consider the drawbacks of using code spread out over several Mathematica cells and discuss the solution of using a Module to put several lines of code together.  We consider local and global variables and their scopes, side effects of Modules, as well as indentation for readability and other aspects of good programming style.

Mathematica operations/structures introduced: 
     Module
     With[]
     variable scope

Modules and Organization

Organizing Code

Entering code into Mathematica line by line, as you will almost certainly have concluded by now, can be dangerous.  Variables may have not been defined when you thought they were, or---perhaps worse---they have changed in value from what they should have been.  The same is true for functions, and indeed for all global  names in Mathematica.  This danger is common to nearly every programming language and demands attention.

Another (related) difficulty with Mathematica in its line by line usage is that the user must "evaluate" several cells to complete a task; there is no general "organization" to the different pieces of a programming task (except in the bracketing of the cells).  While this difficulty is more common to interpreted languages like Mathematica than to compiled languages like C, Fortran, or Pascal, the solution to it (and to the danger above) makes Mathematica more similar to compiled languages.  The solution is to use a Module.

To see the utility of a Module, let's examine several alternatives to performing a sequence of operations in Mathematica which declare some variables and functions and compute some intermediate values.  The subject of our examinations will be an efficient method to find, given real numbers (Subscript[y, 1]), (Subscript[y, 2]), to (Subscript[y, n]), an expression for the polynomial of degree n - 1 which passes through the points (1, (Subscript[y, 1])), (2, (Subscript[y, 2])), and so forth, to (1, (Subscript[y, n])).  We will leave a proof of the result to a numerical analysis course.  First we show how the interpolation works, and then we examine ways to improve the code using some techniques we know now, without using a Module.

Interpolation

Let's begin with six points, (1, (Subscript[y, 1])), (2, (Subscript[y, 2])), up to (6, (Subscript[y, 6])), where the y values are 4, 8, –3, 16, 7, and 0.  We will start by placing x values and y values in separate lists. We use Range[] to generate the numbers 1 through 6.

xs = Range[6]

ys = {4,8,-3,16,7,0}

To find the points through which we are interpolating, we can always use Transpose[]:

pts = Transpose[{xs,ys}]

Now Mathematica has its own function to find the polynomial we are looking for, called InterpolatingPolynomial[], for which the first argument is the list of points and the second argument is the name of the variable for the polynomial.  In the next input cell, we ask Mathematica to find and to plot this polynomial in order to verify that it satisfies our conditions.

(For those interested, the following cell contains two interesting pieces of code:  prolog instructions, which provide the large disks around the points corresponding to our list pts, and a correct use of Set[], using the equals sign, instead of SetDelayed[], using the colon-equals sign.  We use Set[] because we require InterpolatingPolynomial[] to be executed immediately and then assigned to f; we do not want the value of the argument of f substituted for x in the expression first.)

Clear[f, x]
f[x_] = InterpolatingPolynomial[pts,x]
Plot[f[x],{x,1,6},Prolog->{PointSize[0.03],
   RGBColor[1,0,0],Map[Point,pts]}]

Now we could use Expand[] or Simplify[] to simplify the polynomial, but the current form is of most interest:  how did Mathematica find the numbers 4, 4, -15/2, 15/2, -103/24, and 191/120?  (Notice that the other numbers simply correspond to the integers 1 through 5.)  Our goal in what follows is not to produce the polynomial, since we can have Mathematica do so already, but rather to explore a simple and efficient method, called divided difference quotients, which produces these mysterious numbers.

First we define a function diff[] which takes a list and forms the successive differences of elements of a list.  Note that we saw a similar function in section 7.

diff[list_] := Drop[list,1] - Drop[list,-1]

First we begin with ys:

ys

Then we apply diff[] repeatedly, dividing by a successive integer each time, beginning with 1.

diff[%]/1

diff[%]/2

diff[%]/3

diff[%]/4

diff[%]/5

Notice the first element of each of the last 6 lists!

We can figure out the correct denominator at each stage by examining the length of the list.  If we do so, we can define a function divDiff[] and use NestList[] to perform our operations for us.

divDiff[list_]:= (Drop[list,1]-Drop[list,-1])/
  (7-Length[list])

NestList[divDiff,ys,5]

Map[First,%]

Improving Code

Now we may begin to consider how we might improve this code.  We begin then with the following lines:

ys = {4,8,-3,16,7,0}

divDiff[list_]:= (Drop[list,1]-Drop[list,-1]) / (7-Length[list])

NestList[divDiff,ys,5]

Map[First,%]

First, it would be best to execute Clear[divDiff] in front so we don't run into problems later with other definitions of divDiff[].  (Note that variable names used as formal parameters---ys and list in our case---do not present this danger and do not need to be separately cleared.)  Also, we should place it all in one cell so that the user is prevented from executing only some instead of all of the commands.  Let's do that.

Clear[divDiff]
ys = {4,8,-3,16,7,0}
divDiff[list_]:= (Drop[list,1]-Drop[list,-1])/(7-Length[list])
NestList[divDiff,ys,5]
Map[First,%]

To an experienced eye, this code has several problems.  First of all, it does not look very readable.  An important programming principle is that code should be as readable as possible, since you may be bequeathing your code onto someone else. While this may not seem important in this class, imagine you're working for a bank or the national defense system.  Life is too short to cause others too many difficulties with your work!  To clean it up, we'll indent so that a command that runs onto another line is clearly indicated.  Another problem is that we see too many intermediate results.  We'd really just like the last ones, so we'll place semicolons after every expression save the last one.

Clear[divDiff];
ys = {4,8,-3,16,7,0};
divDiff[list_] := (Drop[list,1]-Drop[list,-1]) / 
   (7-Length[list]);
NestList[divDiff,ys,5];
Map[First,%]

What if we wanted to make this a function easily executed by the user with different values?  Specifically, suppose we would like to have the process work on a list of 6 elements of our own choosing?  We could try the following, changing the variable ys to a parameter and adding a function doDivDiff[].  Here our indentation makes more of a difference:

Clear[divDiff,doDivDiff];
divDiff[list_] := (Drop[list,1]-Drop[list,-1]) / 
   (7-Length[list]);
doDivDiff[ys_] := 
   Map[
      First,
      NestList[
         divDiff,
         ys,
         5
      ]
   ];

Notice how we vertically line up arguments of the same function, and we align the closing bracket "]" with the beginning of the associated function.  Even with the indentation, however, this code is hard to understand, since we have so many functions inside others in the definition of doDivDiff[].  We might do better to assign a the result of NestList[] to a temporary variable, and then use it in the Map[] command.  We can do so by using "(command; command; command)" syntax, where we are allowed to have several commands completed as part of one expression:

Clear[divDiff,doDivDiff,temp];
divDiff[list_] := (Drop[list,1]-Drop[list,-1]) / 
   (7-Length[list]);
doDivDiff[ys_] := 
   (temp = NestList[
              divDiff,
              ys,
              5
           ];
    Map[First,temp]
   );

doDivDiff[{3,10,20,-5,2,3}]

This all looks fine, except that divDiff[] and temp are now defined for later, which is unnecessary---and perhaps dangerous---if the user never needs their values and might want to use the name temp again.  We'd like to define the two names just in order to define doDivDiff[] and have them otherwise invisible.  We might try

Clear[ys,divDiff,doDivDiff,temp];
divDiff[list_] := (Drop[list,1]-Drop[list,-1]) / 
   (7-Length[list]);
doDivDiff[ys_] := 
   (temp = NestList[
              divDiff,
              ys,
              5
           ];
    Map[First,temp]
   );
Clear[temp,divDiff];

doDivDiff[{3,10,20,-5,2,3}]

But now Mathematica's forgotten what divDiff[] means altogether.  Frustrated?  Do you think the code is looking too complicated for what little we want to do?  Good.  The solution to these sometimes competing concerns of readability, limited definition of names, and encapsulation into one cell or structure is a Module.

Modules

A Module allows the Mathematica programmer to code a sequence of operations together with some local  names (variables, functions, and so on).  These local names will not be usable by any code outside the module, so the user is safe.  Also, a Module allows the sequence of operations to be encapsulated simply in a programming unit, so that we do not need to rely on a cell.

The syntax of a Module is the following:  Module[{a,b,...}, instruction; instruction; etc.].  The result of the last instruction will be the result of the Module.  (Note that if we place a semicolon after the last instruction, then Mathematica will insert another instruction, Null at the end, and this will be the result of our Module!)  The names we place in the list {a,b,...} (the first argument) will be the local variables; every time we use them in the instructions, the names will refer to the local variables, even if there are some global variables with the same name.

Let's look at our interpolator in Module form:

Clear[doDivDiff];
doDivDiff[ys_] := Module[{temp,divDiff},
   divDiff[list_] := (Drop[list,1]-Drop[list,-1]) / 
      (7-Length[list]);
   temp = NestList[
              divDiff,
              ys,
              5
           ];
   Map[First,temp]
   ];

doDivDiff[{3,10,20,-5,2,3}]

This code is much cleaner;  the indentation works well and does not distract; the function divDiff[] is only locally defined; we are permitted to name our intermediate value temp instead of nesting the functions all in huge command as before.  In short, we get everything we want.

Caveats, Side Effects, and "Local Memory"

Added functionality always comes at a price. What price Modules?  First, the programmer may inadvertently leave a name meant to be local out of the list in the first argument, or might place a name meant to be global---meant to be used both inside and outside of the Module---in the list.  For this reason, it is best to place a comment  at the beginning of the Module explicitly declaring which global variables the Module may use or change.  A comment is text that Mathematica ignores while interpreting your code, and the syntax is (* comment  *).  When we explicitly state what global variables might change, we are admitting what the possible "side effects" of the Module are.

As an example, suppose we want to keep track of names in a sports roster.  We want the roster itself, as well as a variable saying how many people are in the roster, to be global, and we want to define Modules which add or delete names from the list and insure that there are no duplicates.  Here's a possible solution:

Clear[sportsNames,totalNames,addName,deleteName]
sportsNames = {};
totalNames = 0;
addName[s_] := 
   Module[{},
	     (* uses sportsNames, totalNames *)
	     sportsNames = Union[sportsNames,{s}];
	     totalNames = Length[sportsNames];
	  ]
deleteName[s_] :=
	  Module[{},
	  	  (* uses sportsNames, totalNames *)
	  	  sportsNames = Complement[sportsNames,{s}];
	  	  totalNames = Length[sportsNames];
	  ]

addName["Magic"]
addName["Shaq"]
totalNames

Our solution is both efficient, easy to read, and it admits up front what global variables are changed when addName[] and deleteName[] are used.  The two functions have a very definite, desirable side effect, namely, updating sportsNames and totalNames.

A second caveat is that Modules do not remember the values of the local variables inside them when they are executed again.  For instance, we could not make totalNames local to either Module, even if we were only using addName:

Clear[sportsNames,totalNames,addName]
sportsNames = {};
addName[s_] := 
   Module[{totalNames},
	     (* uses sportsNames *)
	     sportsNames = Union[sportsNames,{s}];
	     totalNames = totalNames + 1;
	     Print[totalNames]
	  ]

addName["Magic"]
addName["Shaq"]

The problem here is that totalNames was not defined when the Module was first executed, so incrementing it by one caused Mathematica to spill some red ink in confusion.

Initialization of Local Variables, Constants, and Nesting

Let's return to our interpolator.  What if we want to change the fact that there are 6 y-values?  Perhaps we'd like to change it to 7 or 8.  We notice that the "5" inside the function needs to be replaced by one less than the number of elements in the initial list, and the "7" by one greater.  While we can certainly search for the 5 and 7 inside the Module and change them by hand, the next programmer will more easily understand the function if we show that each depends on one fixed number---the number of elements in the list.  Note that the situation would be much worse if our Module contained several 5's and 7's, some of which did pertain to the length and some did not.  Which 5's and 7's would the programmer look to change?  The best thing to do is replace each of these integers with an expression involving the fixed number, which itself should have a name (len, for instance), and initialize the name explicitly at the beginning of the Module.  We can perform the initialization at the beginning of the Module by doing so inside the first list:

Clear[doDivDiff];
doDivDiff[ys_] := Module[
	  {len=7 (* length of list *),
	     temp,divDiff},
   divDiff[list_] := (Drop[list,1]-Drop[list,-1]) / 
      (len+1-Length[list]);
   temp = NestList[
              divDiff,
              ys,
              len-1
          ];
   Map[First,temp]
   ];

doDivDiff[{3,10,20,-5,2,3,2}]

There is a programming term for len, which is constant.  A constant is a variable initialized once and never changed throughout a programming unit.  It is useful for a reader to know that there is no possibility of the constant's changing, so that he or she does not have to be on the lookout for redefinitions of that variable.  Mathematica has a particular construct for such a variable, the With[] construct.  With[] works just like Module, except that the variables in the first list must be initialized and may never change inside.  A legitimate question at this point is whether With or Module is appropriate for doDivDiff[], since we have both local variables and constants.  The answer is both; there is no reason not to nest With constructs and Module constructs, which is to say that you may have one inside the other.  Here's the nice solution:

Clear[doDivDiff];
doDivDiff[ys_] := With[{len=7 (* length of list *)},
   Module[{temp,divDiff},
   divDiff[list_] := (Drop[list,1]-Drop[list,-1]) / 
      (len+1-Length[list]);
   temp = NestList[
              divDiff,
              ys,
              len-1
          ];
   Map[First,temp]
   ]];

doDivDiff[{3,10,20,-5,2,3,2}]

Finally, what if we wanted the function to take a list of any length and determine itself what the length was?  The nicest solution, now that we see we only need to change len, is to initialize the value of len to Length[ys].

Clear[doDivDiff];
doDivDiff[ys_] := With[{len=Length[ys]},
	  Module[{temp,divDiff},
   divDiff[list_] := (Drop[list,1]-Drop[list,-1]) / 
      (len+1-Length[list]);
   temp = NestList[
              divDiff,
              ys,
              len-1
          ];
   Map[First,temp]
   ]];

doDivDiff[{3,10,20,-5,2,3}]

Scope

The foregoing discussion raises an issue:  when a name (variable, function, or what have you) is defined, for what section of the code does it have meaning?  We've seen that names defined outside of any Module are global, meaning that they can be used freely within or outside of any function, while names mentioned in the first argument of a Module are local  to the Module and lose their meaning outside of the Module.  When we want to be specific about the section of code a name has meaning in, we use the word scope; in the context of computer languages, scope refers to the extent of code in which a certain name is defined.  Here is an example.

x = 2; z = 3;      
myModule :=      
   Module[{x,y}, 
      x = z;     
      y = 3;     
   ];            
y = 2;

We must be careful counting the variables here.  We see three global variables, x, y, and z, and two variables x and y which are local to the Module.  The scope of the global variables is then the entire section of code, while the scope of the local variables is the Module itself.  Notice that if a local variable in a Module has the same name as a global variable, it may be "hidden" in the sense that inside the Module we cannot refer to the global variable.  In our example, any occurrence of x inside the module must refer to the local x, not the global x.  Try to decide by hand what the result of the cell is:  what would the values of the global variables x, y, and z be?  (Answer: 2, 2, 3.)

Programming Style

In this section we have touched on several aspects of basic programming style, which we repeat here.  Programmers are expected to use good style when writing programs, just as they are expected to do when writing papers.  Assume that the audience for a program is always a fellow programmer who wants to read the code quickly and not encounter any difficulties.   Part of your evaluation in each assignment will depend on how well you achieve good style, even if your code is syntactically and logically correct.  In particular:

Indent as you nest functions or modules inside each other, keeping successive arguments lined up vertically and closing brackets lined up with the beginning of the function.

Declare to the reader the side effects of your function by using a comment.

Name variables and functions with descriptive names, and capitalize all but the first word in the name if there are several words joined together.

Unless specifically requested otherwise, name intermediate expressions and use the names in successive lines, thus breaking up deeply nested functions such as "Apply[Map[Table[...".