This notebook is part of a draft of a set of elementary Mathematica tutorials, as presented at the 1999 Mathematica Developer Conference, October 20-23, 1999. This draft is provided here to encourage suggestions about these tutorials. You can send suggestions about these tutorials to Wolfram Research Technical Support at the address support@wolfram.com.

Only the first nine parts of the Basic Tutorial are included in this draft.

Basic Tutorial (Part 8 of 10)

8 Programming

In this eighth part of the basic tutorial you will learn about:

• Defining functions
• Patterns and rules
• Local variables
• Programming styles

Using function definitions

Function definitions were introduced briefly in the first part of this tutorial. Basic information about function definitions can be found in Sections 1.7.1 and 2.4.6 of The Mathematica Book. The use of function definitions as programs is discussed in Section 1.7.2 of The Mathematica Book.

In Mathematica programming, function definitions are used to set up a convenient way to access a program and for organizing the components, or subroutines, of a program.

To illustrate the use of function definitions in Mathematica programming, here is a program that plots a list of numbers. The resulting plot includes horizontal grid lines showing the mean of the numbers in the list, the average of the numbers below the mean, and the average of the numbers above the mean.

For present purposes it is not important to understand exactly how this program works. It is important only to observe that this program consists of a sequence of assignments that are used to process the data and construct the result. For an introductory discussion of programming using sequences of operations see Section 1.2.6 in The Mathematica Book.

In this program the input data are assigned as the value of data. The data in this example represent high and low daily temperatures. In the plot, the upper grid line shows the average high temperature and the lower grid line shows the average low temperature.

data = {12.5, 31.0, 14.5, 33.5, 15.0, 30.0, 14.0, 29.5, 9.5, 35.5, 
          18.0, 32.0, 14.5, 29.0, 12.5};
av = Plus @@ data/Length[data];
hi = Select[data, # > av &];
hi = Plus @@ hi/Length[hi];
lo = Select[data, (# < av) &];
lo = Plus @@ lo/Length[lo];
lab = 
        StringForm["mean temperature = ``\naverage high = ``\naverage low = \
``", PaddedForm[av, 3], PaddedForm[hi, 3], 
          PaddedForm[lo, 3]];
ListPlot[data, 
      GridLines -> {{}, {hi, lo, av}}, 
Frame -> 
        True, PlotStyle -> PointSize[0.02], 
      PlotLabel -> lab]

Here is another version of the same program. This version of the program is set up as a definition of the function TemperaturePlot. This definition provides a convenient way to access the functionality of this program. The program is used by including the data as the argument of the TemperaturePlot function and evaluating the function.

mean[p_] := Plus @@ p/
        Length[p]

TemperaturePlot[data_] := 
      Module[{av, hi, lo, lab}, 
av = 
          mean[data]; 
hi = 
          Select[data, (# > av) &]; 
hi = 
          mean[hi]; 
lo = 
          Select[data, (# < av) &]; 
lo = 
          mean[lo]; 
lab = 
          StringForm["mean temperature = ``\naverage high = ``\naverage low \
= ``", PaddedForm[av, 3], PaddedForm[hi, 3], 
            PaddedForm[lo, 3]]; 
ListPlot[data, 
          GridLines -> {{}, {hi, lo, 
                av}}, 
Frame -> True, 
          PlotStyle -> PointSize[0.02], 
          PlotLabel -> lab]
]

The function mean, which computes the mean of a list of numbers, serves as a subroutine which is called by the TemperaturePlot function. Large Mathematica programs are often organized as a collection of function definitions.

Another feature of this version of the program is the use of Module to localize the variables that are used in the program.  Local variables are discussed in the next section of this tutorial.

Here is an input showing the use of the TemperaturePlot function. This input uses the value of data that was assigned in the previous version of the program. This calculation could also be done by entering the original list of numbers as the argument of TemperaturePlot.

TemperaturePlot[data]

Any Mathematica program can be written either as a sequence of steps or as a collection of function definitions. The choice is often a matter of personal preference. Most large programs, however, and especially programs that will be used more than once, or programs that will be called from other programs, are written as a collection of function definitions.

Local variables

The two most general functions in Mathematica for localizing variables are Module and Block. Basic information about variable localization and about the use of these functions can be found in Sections 1.7.2, 2.6.1 and 2.6.6 of The Mathematica Book.

Local variables are used to protect operations within a program from unwanted effects of definitions outside of the program, and to prevent definitions within a program from influencing operations outside of the program.

The first argument in Block or Module gives a list of variables to be localized, and the second argument gives an expression to be evaluated. The second argument can be any expression. This expression is typically referred to as the body of the Block or Module.

Although Block and Module use the same syntax, and can be used interchangeably in many programs, there are some important differences, and these functions are not interchangeable in all programs. For a comparison of Block and Module, see Section 2.6.7 in The Mathematica Book, which includes an example in these functions are not equivalent. The differences between these functions can appreciated by looking at a few examples, and understanding how these functions work.

In the example in the previous section, Module was used to prevent definitions used inside the program from persisting after the program finished. This underlying issue is illustrated by the following definition of a function f, which assigns a value to x as a side-effect of using the definition.

f[p_] := (x = Cos[p]; x/(1 + x))

f[1]

Since using the rule for f assigns a value to x, subsequent calculations involving x could give confusing results. For example:

Solve[x == 1, x]

This type of problem can be avoided by using Module in the definition of the function.

Clear[x]

f[p_] := Module[{x}, x = Cos[p]; x/(1 + x)]

With this definition, f can be used without the side-effect of assigning a value to x.

f[1]

Solve[x == 1, x]

(At this point you may want to clear the values of f and x so that those values will not interfere with subsequent examples in this tutorial.)

Clear[f, x]

The Module function works by generating new variables and inserting those variables in place of the variables that are listed in the first element of Module. In most programs you never see the new variables, but there are a few situations in which the new variables will be seen. The new variables will be seen, for example, if they are used in the result of the Module.

Module[{temp, x}, temp = Cos[x]; temp/(1 + temp)]

The Block function works by setting aside the values of the variables listed in its first argument and restoring those values after its second argument is evaluated. In other words, Block sets up an evaluation environment in which the values of the listed variables are localized, and evaluates the body expression in that environment.

Here is an example that shows one way to display data and a curve on the same plot. The Block function is used here to temporarily set the value of $DisplayFunction to Identity so that the intermediate plots are not displayed. The output of the Block is a pair of Graphics expressions, which are displayed using Show. Note that Module would not be appropriate in this example, since it would generate a new variable to use in place of $DisplayFunction.  It is essential here that the variable not be renamed.

Show[
Block[{$DisplayFunction = 
            Identity}, 
{ListPlot[
            Table[{x, Sin[x]}, {x, 0, 3, 0.1}]], 
Plot[
            x - x^3/6, {x, 0, 3}]}]]

Patterns and rules

Patterns are discussed in Section 2.3 of The Mathematica Book. Patterns are used so extensively in Mathematica programming that special notations are available for nearly all pattern expressions. A listing of these notations can be found in The Mathematica Book in Appendix A.5.1.

Patterns are used in Mathematica to represent classes of expressions. For example, the expression f[x_] represents any expression with a head of f and with one element. Expressions in the class of expressions represented by a pattern are said to "match" the pattern. For example, the expression f[1 + π] is said to match the pattern f[x_] because f[1 + π] falls within the class of expressions represented by the pattern f[x_].

A very important feature of pattern matching in Mathematica is the ability to assign names to patterns and parts of patterns. For example, when Mathematica matches the pattern f[x_] and the expression f[1 + π], the name x in the pattern is automatically associated with the element 1 + π in the expression. This association can be used for a variety of purposes, the most common of which is to make substitutions on the right side of a replacement rule.

Here is an example showing the use of a named pattern in a replacement rule. In this example, the expression associated with the named pattern x_ on the left side of the replacement rule is inserted in place of x in g[0, x] on the right side of the rule.

f[1 + π] /. f[x_] -> g[0, x]

Exactly the same pattern matching is done if you use an assignment to set up a definition for a function f. For example, the following assignment constructs a rule much like the rule in the previous example, and associates that rule will the symbol f. That rule is applied automatically whenever an expression that matches the pattern f[x_] is evaluated.

f[x_] = g[0, x]

f[1 + π]

(At this point you may want to clear the definition of f so that this definition will not interfere with subsequent examples in this tutorial.)

Clear[f]

FullForm notation is very useful when working with patterns. For example, the FullForm notation for the pattern f[x_] is f[Pattern[x, Blank[]]].

FullForm[f[x_]]

You can get information about the components of this pattern in the documentation for Pattern and Blank.

Programming styles

There are many ways to write any computer program.This is especially true in Mathematica, since Mathematica supports many different programming styles.

This section shows four different ways to program a function that computes the factorial of a non-negative integer. Since Factorial is a built-in function, one approach is to simply use the built-in function. For example, the following assignment defines a function f that satisfies the objectives of this exercise.

f = Factorial

f[5]

This trivial example illustrates the important fact that many common programming tasks can be handled using built-in functions. When writing programs, it is frequently useful to look in the documentation to find out if there is already a function that will do what you want to do. For general programming, see especially.Sections 1.7, 2.2, 2.5.8 and 2.5.9 in The Mathematica Book.

Another way to program a factorial function is to write a loop that multiples consecutive integers. There are several ways to do that. Here is one possibility.

Clear[f]

f[n_Integer?NonNegative] := 
      Block[{result}, 
result = 1; 
Do[
          result = k result, {k, 1, 
            n}]; 
result
]

{f[-1], f[0], f[1], f[5]}

This style of programming is called procedural programming. Procedural programming is programming based on loops, conditionals, and other functions that control the flow of execution in a sequence of program statements. Mathematica includes all of the functions needed for procedural programming. The basic procedural programming functions in Mathematica are Do, For, While, If, Switch, Which, Goto, Label, and Return.

The pattern n_Integer?NonNevative in this input specifies that this definition should be used only when the argument of f is a non-negative integer. This pattern matches any expression that has a head of Integer and for which NonNegative gives True. In FullForm notation this pattern is

FullForm[n_Integer?NonNegative]

For information about the components of this pattern, see the documentation for Pattern, PatternTest, and Blank.

Procedural programming is a very common programming style. Many programming languages, such as Fortran, C, and Basic, are designed almost exclusively for procedural programming. Although Mathematica is not primarily a procedural programming language, procedural programming is nevertheless fully supported, and is an entirely reasonable way to write programs in Mathematica.

Mathematica is designed primarily for functional programming. Functional programming is programming by applying functions to expressions or to the results of other functions. As with procedural programming, there are many ways to write a functional program. Here is one way to define the factorial function using a functional program. In this program, the Range function is used to generate a list of consecutive integers, and the Apply function is used to apply Times to that list so that the integers are multiplied together.

Clear[f]

f[n_Integer?NonNegative] := Apply[Times, Range[n]]

{f[-1], f[0], f[1], f[5]}

Another way to define a factorial function is with rule-based programming, which is programming by applying transformation rules. Rule-based programing is often the most natural way to set up calculations that are specified by rules. For example, the factorial function for a non-negative integer can be specified by a rule that defines the factorial of 0 to be 1, and a recursive rule that defines the factorial of any positive integer n to be n multiplied by the factorial of n - 1. These rules can be programmed into Mathematica as a pair definitions for f.

Clear[f]

f[0] = 1

f[n_Integer?Positive] := n f[n - 1]

{f[-1], f[0], f[1], f[5]}

As with other programming styles, there are many ways to write a rule-based program. Here is a program that evaluates the factorial function by applying rules explicitly, rather than by using assignments that set up evaluation rules. This approach to rule-based programming has the advantage of not leaving definitions that might interfere with subsequent calculations.

Clear[f]

{f[-1], f[0], f[1], f[5]} //. {f[0] -> 1, 
        f[n_Integer?Positive] :> n f[n - 1]}

Exercises

Exercise 8.1 Defining a function

Define a function f[n_] that returns the difference between Cos[10  π] and a series expansion to order n of Cos[x] expanded around x == 0.

Here is a one-line program that computes this difference for a series expansion of order 50. You can define your function by inserting n in place of 50 in this program, and using that expression in your definition.

N[Cos[10  π] - Normal[Series[Cos[x], {x, 0, 50}]] /. 
        x -> 10  π]

Use your function (possibly by trial and error) to find the smallest value of n for which this difference is less than 1.

Where you are finished, clear the value of f so that this definition will not interfere with subsequent calculations.

Exercise 8.2 Recursive rules

Consider a function b[n] defined by the two-term recursion relation b[n] == b[n - 1] + 2  b[n - 2], with initial conditions b[0] == b[1] == 1.

Define a function b to compute values of this function for any non-negative integer n. Use your definition to compute a value for b[10].

You can compute the general term in this recursion relation using the RSolve function, which is defined in the standard DiscreteMath`RSolve` package. Here is an example showing the use of this package to find the general term for the recursion relation in this exercise.

<< DiscreteMath`RSolve`

Clear[b, n]

RSolve[{b[n] == b[n - 1] + 2  b[n - 2], b[0] == 1, 
        b[1] == 1}, b[n], n]

Exercise 8.3 Patterns

Here is a definition of a function f which uses the Cases function to select from a list of pairs all of the pairs in which the first element is greater than 5.

f[p : {{_, _} ... }] := Cases[p, q_ /; First[q] > 5]

f[{{2, 4}, {6, 1}, {9, 2}, {4, 8}, {7, 1}}]

Change the definition of f so that it selects from a list of pairs all of the pairs in which the sum of the elements is greater than 10. This function can also be written using Select, if you prefer using test functions rather than patterns.

The pattern p : {{_, _} ... } on the left side of this rule matches any list of zero or more pairs and associates that expression with the name p. The pattern {_, _} matches any pair (a list with two elements), and {{_, _} ... }, or {RepeatedNull[{_, _}]} uses RepeatedNull to set up a pattern that matches any list of zero or more such expressions.

The second argument in Cases is a pattern that specifies the class of expressions to select. In this example, the pattern q_ /; First[q] > 5, or Condition[q_, First[q] > 5], uses Condition in a pattern that matches any expression q such that First[q] > 5 evaluates to True.

Exercise 8.4 Side effects

Define a function that prints the value of its argument and then returns the square of that argument.

Any consequence of evaluation other than the final result is called a side-effect. The result returned by a Print expression, for example, is always Null. The information displayed by evaluation of a Print expression is generated as a side-effect.

Side-effects can be used for a variety of purposes. A function that prints out the value of its argument, for example, can be used to see if the intended arguments that are being passed to the function that is called within a larger program, or to determine if a function is being called.

Here is a function f that increments the value of a global variable k each time it is called. The result of evaluating this function is the square of the argument. Incrementing the global variable is a side-effect. In the example below, this side-effect is used to count the number of times that f is evaluated during evaluation of a numerical integral.

f[p_] := (k++; p^2)

k = 0; NIntegrate[f[x], {x, 1, 2}]; k

As an additional exercise, you might consider how you would define a function that prints the value of its argument periodically, such as every tenth time that the function is called. Such a definition can be used to avoid a large volume of printed output if the function is used very frequently in a program.

Exercise 8.5 Debugging

Here is a procedural program that finds the largest difference between consecutive elements in a list of integers.

f[p_List] := 
      Module[{diff}, 
diff = 0; 
Do[
          If[diff < 
              Abs[p[[k]] - p[[k + 1]]], 
diff = 
              Abs[p[[k]] - p[[k + 1]]]], {k, 1, 
            Length[p]}]; 
diff
]

The result from this program is correct, but the program generates a warning message indicating an attempt to access an element beyond the end of the argument list.

f[{17, 23, 38, 27, 25, 16}]

Write a new definition of f that does not try to access an element beyond the end of the argument list. One easy way to do this is to modify the original procedural program, although another possibility is to rewrite the program, possibly in a different programming style. A very compact version of this program can be written using a functional programming style.

Exercise 8.6 Graphics programming

Here is a short program that constructs Graphics3D expressions for two cones, and displays those two expressions together. The second cone is a flattened into a plane, and resembles a disk rather than a cone.

cone1 = 
      With[{n = 10}, 
        Graphics3D[
          Table[Polygon[{{0, 0, 0}, {Cos[2  π t/n], Sin[2  π t/n], 
                  2}, {Cos[2  π (t + 1)/n], 
                  Sin[2  π (t + 1)/n], 2}}], {t, 1, n}]]]

cone2 = 
      With[{n = 10}, 
        Graphics3D[
          Table[Polygon[{{0, 0, 0}, {Cos[2  π t/n], Sin[2  π t/n], 
                  0}, {Cos[2  π (t + 1)/n], 
                  Sin[2  π (t + 1)/n], 0}}], {t, 1, n}]]]

Show[cone1, cone2]

Construct a third cone of your choice and add your cone to this image. An expression for a new cone can be constructed by copying the program for one of these cones and changing the coordinates that specify the vertices of the polygons.

Polygon vertices are specified as lists with three real numbers. You can move the cones up or down by adding a constant to the third coordinate of each vertex. For a more complicated change, you can rotate a cone to be oriented along a different axis by exchanging vertex coordinates.

Exercise 8.7 Using built-in functions

Here is a function that performs a simple operation on a list.

f[p_List] := 
      Module[{k, 
          result}, 
result = {}; 
For[
          k = 1, k ≤ Length[p], k++, 
If[
            Positive[p[[k]]], 
result = 
              Append[result, 
                p[[k]]]]
]; 
result]

Here is an example showing a typical application of this function.

f[{3, 5, -1, 3, -4, -8, 7}]

Determine what this function does and define a new function that performs that same task using the built-in function Select.

Exercise 8.8 Matrices and part assignments

Consider a program in which a square matrix has been assigned as the value of a symbol m. For example:

m = {{3, 1, 7}, {2, 9, 5}, {4, 1, 7}}; MatrixForm[m]

Write a program that assigns 0 as the value of all of the diagonal elements in m. After running your program, the value of m should give the following result:

MatrixForm[m]

As an example, here is a program that shows one way to do this type of operation. This program assigns zero as the value of all of the elements below the diagonal in a square matrix.

m = {{3, 1, 7}, {2, 9, 5}, {4, 1, 7}};

Do[Do[m[[i, j]] = 0, {j, 1, i - 1}], {i, 2, Length[m]}]

MatrixForm[m]

Exercise 8.9 Vector operations

Define a function Normalize[v] that takes a vector v as an argument and returns the normalized vector.

In this exercise, a vector is a list of numbers, the magnitude of a vector is the square root of the sum of the squares of the elements of the vector, and a normalized vector is a vector with magnitude 1. A non-zero vector can be normalized by dividing the vector by the magnitude of the vector.

As an example, here is a function Magnitude[v] that returns the magnitude of a vector v.

Magnitude[v_List] := Sqrt[v . v]

Magnitude[{v1, v2, v3}]

Magnitude[{2.7, 3.0, 1.5}]

The Magnitude function can be used in the definition of Normalize.

Exercise 8.10 Writing a package

Write a package that contains the definitions of Magnitude and Normalize from the previous exercise, and load that package in to Mathematica.

A package is a file containing Mathematica inputs that can be read in to Mathematica. You can create a package using Mathematica, or using any ordinary text editor.

To create a package using Mathematica, open a new notebook and copy the definitions of Magnitude and Normalize into that notebook. You can use the definitions from the previous exercise, or the following definitions:

Magnitude[v_List] := Sqrt[v . v]

Normalize[v_List] := v/Magnitude[v]

Now set the cells into which you entered these definitions to be initialization cells. You can do this by selecting the cell brackets and choosing Initialization Cell from the Cell Properties submenu of the Cell menu. The notebook is now ready to be saved as a package.

To create a package using another text editor, open a new file with a text editor and copy the definitions of Magnitude and Normalize into that file.

After the package has been created, it must be saved in a location where Mathematica can find it. Mathematica looks for packages in the locations listed in the value of the global variable $Path. You can see the list of locations that will be used on your computer by evaluating $Path.

If you created the package file as a Mathematica notebook, you can save the package by selecting Package Format from the Save As Special... submenu of the File menu. This will bring up a dialog box into which you can enter the name of the package. For example, you might save this package in the file VectorOperations.m. The names of Mathematica package files conventionally end in the extension .m, but that is not a requirement. For the purpose of this exercise, you can use any name that you want.

If you created the package file using a separate text editor, use the text editor to save the file. Be sure to save the file in one of the locations listed in the value of $Path.

To demonstrate that the package has been created correctly, clear the values of 
Magnitude and Normalize and load the package in to Mathematica. In the example shown here, the package is loaded using the Get function.

Clear[Magnitude, Normalize]

Get["VectorOperations.m"]

?Magnitude

?Normalize

In case of difficulty, check that the name of the package has been spelled correctly, and that the package file has been saved in one of the locations listed in the value of $Path.