(*^
::[ Information =
"This is a Mathematica Notebook file. It contains ASCII text, and can be
transferred by email, ftp, or other text-file transfer utility. It should
be read or edited using a copy of Mathematica or MathReader. If you
received this as email, use your mail application or copy/paste to save
everything from the line containing (*^ down to the line containing ^*)
into a plain text file. On some systems you may have to give the file a
name ending with ".ma" to allow Mathematica to recognize it as a Notebook.
The line below identifies what version of Mathematica created this file,
but it can be opened using any other version as well.";
FrontEndVersion = "Macintosh Mathematica Notebook Front End Version 2.2";
MacintoshStandardFontEncoding;
fontset = title, inactive, noPageBreakBelow, nohscroll, preserveAspect,
groupLikeTitle, center, M7, bold, e8, 24, "Times";
fontset = subtitle, inactive, noPageBreakBelow, nohscroll, preserveAspect,
groupLikeTitle, center, M7, bold, e6, 18, "Times";
fontset = subsubtitle, inactive, noPageBreakBelow, nohscroll,
preserveAspect, groupLikeTitle, center, M7, italic, e6, 14, "Times";
fontset = section, inactive, noPageBreakBelow, nohscroll, preserveAspect,
groupLikeSection, grayBox, M22, bold, a20, 18, "Times";
fontset = subsection, inactive, noPageBreakBelow, nohscroll,
preserveAspect, groupLikeSection, blackBox, M19, bold, a15, 14, "Times";
fontset = subsubsection, inactive, noPageBreakBelow, nohscroll,
preserveAspect, groupLikeSection, whiteBox, M18, bold, a12, 12, "Times";
fontset = text, inactive, nohscroll, noKeepOnOnePage, preserveAspect, M7,
12, "Times";
fontset = smalltext, inactive, nohscroll, noKeepOnOnePage, preserveAspect,
M7, 10, "Times";
fontset = input, noPageBreakInGroup, preserveAspect, groupLikeInput, M42,
N23, bold, B65535, L-5, 12, "Courier";
fontset = output, output, inactive, noPageBreakInGroup, preserveAspect,
groupLikeOutput, M42, N23, L-5, 12, "Courier";
fontset = message, inactive, noPageBreakInGroup, preserveAspect,
groupLikeOutput, M42, N23, R65535, L-5, 12, "Courier";
fontset = print, inactive, noPageBreakInGroup, preserveAspect,
groupLikeOutput, M42, N23, L-5, 12, "Courier";
fontset = info, inactive, noPageBreakInGroup, preserveAspect,
groupLikeOutput, M42, N23, B65535, L-5, 12, "Courier";
fontset = postscript, PostScript, formatAsPostScript, output, inactive,
noPageBreakInGroup, preserveAspect, groupLikeGraphics, M7, l34, w282, h287,
12, "Courier";
fontset = name, inactive, nohscroll, noKeepOnOnePage, preserveAspect, M7,
italic, 10, "Geneva";
fontset = header, inactive, noKeepOnOnePage, preserveAspect, M7, 12, "Times";
fontset = leftheader, inactive, L2, 12, "Times";
fontset = footer, inactive, noKeepOnOnePage, preserveAspect, center, M7,
12, "Times";
fontset = leftfooter, inactive, L2, 12, "Times";
fontset = help, inactive, nohscroll, noKeepOnOnePage, preserveAspect, M7,
10, "Times";
fontset = clipboard, inactive, nohscroll, noKeepOnOnePage, preserveAspect,
M7, 12, "Times";
fontset = completions, inactive, nohscroll, noKeepOnOnePage,
preserveAspect, M7, 12, "Times";
fontset = special1, inactive, nohscroll, noKeepOnOnePage, preserveAspect,
M7, 12, "Times";
fontset = special2, inactive, nohscroll, noKeepOnOnePage, preserveAspect,
M7, 12, "Times";
fontset = special3, inactive, nohscroll, noKeepOnOnePage, preserveAspect,
M7, 12, "Times";
fontset = special4, inactive, nohscroll, noKeepOnOnePage, preserveAspect,
M7, 12, "Times";
fontset = special5, inactive, nohscroll, noKeepOnOnePage, preserveAspect,
M7, 12, "Times";
paletteColors = 128; currentKernel;
]
:[font = smalltext; inactive; preserveAspect]
Copyright (C) 1997 Rich Neidinger, John Swallow, and Todd Will. Free for
distribution to college and university instructors for personal,
non-commercial use. If these notebooks are used in a course, the authors
request $20 per student.
:[font = title; inactive; preserveAspect]
9. Modules
:[font = smalltext; inactive; preserveAspect]
Last revision: September 25 1996
:[font = text; inactive; preserveAspect]
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.
:[font = section; inactive; Cclosed; preserveAspect; startGroup]
Modules and Organization
:[font = subsection; inactive; Cclosed; preserveAspect; startGroup]
Organizing Code
:[font = text; inactive; preserveAspect]
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.
;[s]
3:0,0;313,1;320,0;426,-1;
2:2,13,9,Times,0,12,0,0,0;1,13,9,Times,2,12,0,0,0;
:[font = text; inactive; preserveAspect]
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.
:[font = text; inactive; preserveAspect]
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
"y1", "y2", to "yn", an expression for the polynomial of degree "n-1" which
passes through the points (1,y1), (2,y2), and so forth, to (1,yn). 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.
:[font = subsubsection; inactive; Cclosed; preserveAspect; startGroup]
Interpolation
:[font = text; inactive; preserveAspect]
Let's begin with six points, (1,y1), (2,y2), up to (6,y6), 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.
:[font = input; preserveAspect]
xs = Range[6]
:[font = input; preserveAspect]
ys = {4,8,-3,16,7,0}
:[font = text; inactive; preserveAspect]
To find the points through which we are interpolating, we can always use
"Transpose[ ]":
:[font = input; preserveAspect]
pts = Transpose[{xs,ys}]
:[font = text; inactive; preserveAspect]
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.
:[font = text; inactive; preserveAspect]
(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.)
:[font = input; preserveAspect]
Clear[f]
f[x_] = InterpolatingPolynomial[pts,x]
Plot[f[x],{x,1,6},Prolog->{PointSize[0.03],
RGBColor[1,0,0],Map[Point,pts]}]
:[font = text; inactive; preserveAspect]
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.
;[s]
3:0,0;440,1;468,0;514,-1;
2:2,13,9,Times,0,12,0,0,0;1,13,9,Times,2,12,0,0,0;
:[font = text; inactive; preserveAspect]
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.
:[font = input; preserveAspect]
diff[list_] := Drop[list,1] - Drop[list,-1]
:[font = text; inactive; preserveAspect]
First we begin with "ys":
:[font = input; preserveAspect]
ys
:[font = text; inactive; preserveAspect]
Then we apply "diff[ ]" repeatedly, dividing by a successive integer each
time, beginning with 1.
:[font = input; preserveAspect]
diff[%]/1
:[font = input; preserveAspect]
diff[%]/2
:[font = input; preserveAspect]
diff[%]/3
:[font = input; preserveAspect]
diff[%]/4
:[font = input; preserveAspect]
diff[%]/5
:[font = text; inactive; preserveAspect]
Notice the first element of each of the last 6 lists!
:[font = text; inactive; preserveAspect]
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.
:[font = input; preserveAspect]
divDiff[list_]:= (Drop[list,1]-Drop[list,-1])/
(7-Length[list])
:[font = input; preserveAspect]
NestList[divDiff,ys,5]
:[font = input; preserveAspect; endGroup]
Map[First,%]
:[font = subsubsection; inactive; Cclosed; preserveAspect; startGroup]
Improving Code
:[font = text; inactive; preserveAspect]
Now we may begin to consider how we might improve this code. We begin then
with the following lines:
:[font = input; preserveAspect]
ys = {4,8,-3,16,7,0}
:[font = input; preserveAspect]
divDiff[list_]:= (Drop[list,1]-Drop[list,-1]) / (7-Length[list])
:[font = input; preserveAspect]
NestList[divDiff,ys,5]
:[font = input; preserveAspect]
Map[First,%]
:[font = text; inactive; preserveAspect]
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.
:[font = input; preserveAspect]
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,%]
:[font = text; inactive; preserveAspect]
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.
:[font = input; preserveAspect]
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,%]
:[font = text; inactive; preserveAspect]
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:
:[font = input; preserveAspect]
Clear[divDiff,doDivDiff];
divDiff[list_] := (Drop[list,1]-Drop[list,-1]) /
(7-Length[list]);
doDivDiff[ys_] :=
Map[
First,
NestList[
divDiff,
ys,
5
]
];
:[font = text; inactive; preserveAspect]
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:
:[font = input; preserveAspect]
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]
);
:[font = input; preserveAspect]
doDivDiff[{3,10,20,-5,2,3}]
:[font = text; inactive; preserveAspect]
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
:[font = input; preserveAspect]
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];
:[font = input; preserveAspect]
doDivDiff[{3,10,20,-5,2,3}]
:[font = text; inactive; preserveAspect; endGroup; endGroup]
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.
:[font = subsection; inactive; Cclosed; preserveAspect; startGroup]
Modules
:[font = text; inactive; preserveAspect]
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.
;[s]
3:0,0;95,1;102,0;375,-1;
2:2,13,9,Times,0,12,0,0,0;1,13,9,Times,2,12,0,0,0;
:[font = text; inactive; preserveAspect]
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. It is important to remember that all portions of a Module should
reside in the same Mathematica cell.
:[font = text; inactive; preserveAspect]
Let's look at our interpolator in Module form:
:[font = input; preserveAspect]
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]
];
:[font = input; preserveAspect]
doDivDiff[{3,10,20,-5,2,3}]
:[font = text; inactive; preserveAspect; endGroup]
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.
:[font = subsection; inactive; Cclosed; preserveAspect; startGroup]
Caveats, Side Effects, and "Local Memory"
:[font = text; inactive; preserveAspect]
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.
;[s]
3:0,0;329,1;336,0;684,-1;
2:2,13,9,Times,0,12,0,0,0;1,13,9,Times,2,12,0,0,0;
:[font = text; inactive; preserveAspect]
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:
:[font = input; preserveAspect]
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];
]
:[font = input; preserveAspect]
addName["Magic"]
addName["Shaq"]
totalNames
:[font = text; inactive; preserveAspect]
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".
:[font = text; inactive; preserveAspect]
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":
:[font = input; preserveAspect]
Clear[sportsNames,totalNames,addName]
sportsNames = {};
addName[s_] :=
Module[{totalNames},
(* uses sportsNames *)
sportsNames = Union[sportsNames,{s}];
totalNames = totalNames + 1;
Print[totalNames]
]
:[font = input; preserveAspect]
addName["Magic"]
addName["Shaq"]
:[font = text; inactive; preserveAspect; endGroup]
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.
:[font = subsection; inactive; Cclosed; preserveAspect; startGroup]
Initialization of Local Variables, Constants, and Nesting
:[font = text; inactive; preserveAspect]
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:
:[font = input; preserveAspect]
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]
];
:[font = input; preserveAspect]
doDivDiff[{3,10,20,-5,2,3,2}]
:[font = text; inactive; preserveAspect]
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:
;[s]
5:0,0;47,1;56,0;496,1;500,0;875,-1;
2:3,13,9,Times,0,12,0,0,0;2,13,9,Times,2,12,0,0,0;
:[font = input; preserveAspect]
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]
]];
:[font = input; preserveAspect]
doDivDiff[{3,10,20,-5,2,3,2}]
:[font = text; inactive; preserveAspect]
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]".
:[font = input; preserveAspect]
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]
]];
:[font = input; preserveAspect; endGroup]
doDivDiff[{3,10,20,-5,2,3}]
:[font = subsection; inactive; Cclosed; preserveAspect; startGroup]
Scope
:[font = text; inactive; preserveAspect]
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.
;[s]
7:0,0;214,1;220,0;354,1;359,0;515,1;520,0;653,-1;
2:4,13,9,Times,0,12,0,0,0;3,13,9,Times,2,12,0,0,0;
:[font = input; preserveAspect]
x = 2; z = 3;
myModule :=
Module[{x,y},
x = z;
y = 3;
];
y = 2;
:[font = text; inactive; preserveAspect; endGroup]
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.)
:[font = subsection; inactive; Cclosed; preserveAspect; startGroup]
Programming Style
:[font = text; inactive; preserveAspect]
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:
:[font = text; inactive; preserveAspect]
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.
:[font = text; inactive; preserveAspect]
Declare to the reader the side effects of your function by using a comment.
:[font = text; inactive; preserveAspect]
Name variables and functions with descriptive names, and capitalize all but
the first word in the name if there are several words joined together.
:[font = text; inactive; preserveAspect; endGroup; endGroup]
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[...".
^*)