Information in this document is subject to change without notice and does not
represent a commitment on the part of the vendor. The software described in
this document is furnished under a license agreement and may be used or
copied only in accordance with the agreement. It is against the law to copy the
software on any medium except as specifically allowed in the agreement.
Windows is a registered trademark of Microsoft Corporation.
Java and all Java based marks are trademarks or registered trademarks of Sun
Microsystems, Inc. in the United States and other countries.
Maplesoft is independent of Sun Microsystems, Inc.
All other trademarks are the property of their respective owners.
This document was produced using a special version of Maple that reads and
updates LaTeX files.
This manual describes advanced MapleTMprogramming concepts, including:
• Variable scope, procedures, modules, and packages
• Advanced input and output
• Numerical programming
• Programming with Maple plots
• Connectivity: translating Maple code to other programming lan-
guages, calling external libraries from Maple, and calling Maple code
from external libraries
• Internal representation and manipulation
Audience
This manual provides information for experienced Maple programmers.
You should be familiar with the following.
• Maple Online Help Introduction
• Example worksheets
• How to use Maple interactively
• The
Introductory Programming Guide
1
2 • Preface
Worksheet Graphical Interface
You can access the power of the Maple computation engine through a variety of user interfaces: the standard worksheet, the command-line1version,
r
the classic worksheet (not available on Macintosh
), and custom-built
MapletTMapplications. The full Maple system is available through all of
these interfaces. In this manual, any references to the graphical Maple
interface refer to the standard worksheet interface. For more information
on the various interface options, refer to the ?versions help page.
Manual Set
There are three other manuals available for Maple users, the
Maple Get-
ting Started Guide, the Maple User Manual, and the Maple Introductory Programming Guide.
2
• The Maple Getting Started Guide provides extensive information
for new users on using Maple, and the resources available in the software and on the Maplesoft Web site (http://www.maplesoft.com).
• The
Maple User Manual provides an overview of the Maple software
including Document and Worksheet modes, performing computations,
creating plots and animations, creating and using Maplets, creating
mathematical documents, expressions, basic programming information, and basic input and output information.
• The Maple Introductory Programming Guide introduces the basic
Maple programming concepts, such as expressions, data structures,
looping and decision mechanisms, procedures, input and output, debugging, and the Maplet User Interface Customization System.
The Maple software also has an online help system. The Maple help system allows you to search in many ways and is always available. There are
also examples that you can copy, paste, and execute immediately.
1
The command-line version provides optimum performance. However, the worksheet
interface is easier to use and renders typeset, editable math output and higher quality
plots.
2
The Student Edition does not include the Maple Introductory Programming Guide
and the Maple Advanced Programming Guide. These programming guides can be pur-
chased from school and specialty bookstores or directly from Maplesoft.
Conventions •3
Conventions
This manual uses the following typographical conventions.
• courier font - Maple command, package name, and option name
• bold roman font - dialog, menu, and text field
italics - new or important concept, option name in a list, and manual
•
titles
• Note - additional information relevant to the section
• Important - information that must be read and followed
Customer Feedback
Maplesoft welcomes your feedback. For suggestions and comments related
to this and other manuals, email doc@maplesoft.com
.
4 • Preface
1Procedures, Variables,
and Extending Maple
Prerequisite Knowledge
Before reading this chapter, you must have an understanding of Maple
evaluation rules for variables and parameters as described in chapter 6 of
Introductory Programming Guide.
the
In This Chapter
Nested ProceduresYou can define a Maple procedure within another
Maple procedure.
Procedures That Return ProceduresYou can create procedures that
return procedures by using Maple evaluation rules.
Local VariablesLocal variables can exist after the procedure which created them has exited. This feature allows a procedure to return a procedure. The new procedure requires a unique place to store information.
Interactive InputYou can write interactive procedures, querying the
user for missing information or creating an interactive tutorial or a test.
Extending MapleThe Maple software includes useful mechanisms for
extending Maple functionality, which reduce the need to write specialpurpose procedures. Several Maple commands can be extended.
1.1Nested Procedures
You can define a Maple procedure inside another Maple procedure. Some
Maple commands are very useful inside a procedure. In the worksheet
5
6 • Chapter 1: Procedures, Variables, and Extending Maple
environment, the map command is used to apply an operation to the
elements of a structure. For example, you can divide each element of a
list by a number, such as 8.
>
lst := [8, 4, 2, 16]:
>
map( x->x/8, lst);
1
1
,
[1,
, 2]
2
4
Consider a variation on the map command, which appears in the fol-
lowing procedure.
ExampleThis new procedure divides each element of a list by the first
element of that list.
>
nest := proc(x::list)
>
local v;
>
v := x[1];
>
map( y -> y/v, x );
>
end proc:
>
nest(lst);
1
1
,
[1,
, 2]
2
4
The procedure nest contains a second procedure, map, which in this
case is the Maple command map. Maple applies its lexical scoping rules,
which declare the v within the call to map as the same v as in the outer
procedure, nest.
Scoping Rules
This section explains Maple scoping rules. You will learn how Maple determines which variables are local to a procedure and which are global.
You must have a basic understanding of Maple evaluation rules for parameters, and for local and global variables. For more information, refer
to chapter 6 of the
Introductory Programming Guide.
Local Versus Global Variables
In general, when writing a procedure, you should explicitly declare which
variables are global and which are local. Declaring the scope of the variables makes your procedure easier to read and debug. However, sometimes
declaring the variables is not the best method. In the previous nest procedure, the variable in the map command is defined by the surrounding
1.1 Nested Procedures• 7
procedure. What happens if you define this variable, v, as local to the
invocation of the procedure within map?
>
nest2 := proc(x::list)
>
local v;
>
v := x[1];
>
map( proc(y) local v; y/v; end, x );
>
end proc:
>
nest2(lst);
4
2
8
,
[
v
16
,
,
v
]
v
v
The nest2 procedure produces different results. When the variables
are declared in the inner procedure, the proper values from the enclosing
procedure are not used. Either a variable is local to a procedure and
certain procedures that are completely within it, or it is global to the
entire Maple session.
RuleMaple determines whether a variable is local or global, from the
inside procedure to the outside procedure. The name of the variable is
searched for among:
1. Parameters of the inner procedure
2. Local declarations and global declarations of the inner procedure
3. Parameters of the outside procedure
4. Local and global declarations of the outside procedure
5. Implicitly declared local variables of any surrounding procedure(s)
If found, that specifies the binding of the variable.
If, using the above rule, Maple cannot determine whether a variable
is global or local, the following default decisions are made.
• If a variable appears on the
left side of an explicit assignment or as
the controlling variable of a for loop, Maple regards the variable as
local.
• Otherwise, Maple regards the variable as global to the whole session.
In particular, Maple assumes by default that the variables you pass as
arguments to other procedures, which may set their values, are global.
8 • Chapter 1: Procedures, Variables, and Extending Maple
The Quick-Sort Algorithm
Sorting a few numbers is quick using any method, but sorting large
amounts of data can be very time consuming; thus, finding efficient methods is important.
The following quick-sort algorithm is a classic algorithm. The key to
understanding this algorithm is to understand the operation of partitioning. This involves choosing any one number from the array that you are
about to sort. Then, you reposition the numbers in the array that are less
than the number that you chose to one end of the array and reposition
numbers that are greater to the other end. Lastly, you insert the chosen
number between these two groups.
At the end of the partitioning, you have not yet entirely sorted the
array, because the numbers less than or greater than the one you chose
may still be in their original order. This procedure divides the array into
two smaller arrays which are easier to sort than the original larger one.
The partitioning operation has thus made the work of sorting much easier. You can bring the array one step closer in the sorting process by
partitioning each of the two smaller arrays. This operation produces four
smaller arrays. You sort the entire array by repeatedly partitioning the
smaller arrays.
Example
The partition procedure uses an array to store the list because you can
change the elements of an array directly. Thus, you can sort the array in
place and not waste any space generating extra copies.
The quicksort procedure is easier to understand if you look at the
procedure partition in isolation first. This procedure accepts an array
of numbers and two integers. The two integers are element numbers of the
array, indicating the portion of the array to partition. While you could
possibly choose any of the numbers in the array to partition around, this
procedure chooses the last element of the section of the array for that
purpose, namely A[n]. The intentional omission of global and local
statements shows which variables Maple recognizes as local and which
are global by default. It is recommended, however, that you not make
this omission in your procedures.
>
partition := proc(A::array(1, numeric),
>
>
>
>
>
>
i := m;
j := n;
x := A[j];
while i<j do
if A[i]>x then
m::posint, n::posint)
1.1 Nested Procedures• 9
>
>
>
>
>
>
>
>
>
>
Warning, ‘i‘ is implicitly declared local to procedure
‘partition‘
Warning, ‘j‘ is implicitly declared local to procedure
‘partition‘
Warning, ‘x‘ is implicitly declared local to procedure
‘partition‘
end do;
A[j] := x;
eval(A);
end proc:
A[j] := A[i];
j := j-1;
A[i] := A[j];
else
i := i+1;
end if;
Maple declares i, j, and x local because the partition procedure con-
tains explicit assignments to those variables. The partition procedure
also assigns explicitly to A, but A is a parameter, not a local variable.
Because you do not assign to the name eval, Maple makes it the global
name which refers to the eval command.
After partitioning the array a in the following, all the elements less
than 3 precede 3 but they are in no particular order; similarly, the elements
larger than 3 come after 3.
>
a := array( [2,4,1,5,3] );
a := [2, 4, 1, 5, 3]
>
partition( a, 1, 5);
[2, 1, 3, 5, 4]
The partition procedure modifies its first argument, changing a.
>
eval(a);
[2, 1, 3, 5, 4]
The final step in assembling the quicksort procedure is to insert
the partition procedure within an outer procedure. The outer procedure first defines the partition subprocedure, then partitions the array.
In general, avoid inserting one procedure in another. However, you will
10 •Chapter 1: Procedures, Variables, and Extending Maple
encounter situations in following sections of this chapter in which it is necessary to nest procedures. Since the next step is to partition each of the
two subarrays by calling quicksort recursively, partition must return
the location of the element which divides the partition.
ExampleThis example illustrates the role of nested procedures. The
outer procedure, quicksort, contains the inner procedure, partition.
>
quicksort := proc(A::array(1, numeric),
>
>
local partition, p;
>
>
partition := proc(m,n)
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
end proc:
i := m;
j := n;
x := A[j];
while i<j do
if A[i]>x then
A[j] := A[i];
j := j-1;
A[i] := A[j];
else
i := i+1;
end if;
end do;
A[j] := x;
p := j;
end proc:
if m<n then# if m>=n there is nothing to do
p:=partition(m, n);
quicksort(A, m, p-1);
quicksort(A, p+1, n);
end if;
eval(A);
m::integer, n::integer)
Warning, ‘i‘ is implicitly declared local to procedure
‘partition‘
Warning, ‘j‘ is implicitly declared local to procedure
‘partition‘
Warning, ‘x‘ is implicitly declared local to procedure
‘partition‘
>
a := array( [2,4,1,5,3] );
a := [2, 4, 1, 5, 3]
1.1 Nested Procedures• 11
>
quicksort( a, 1, 5);
[1, 2, 3, 4, 5]
>
eval(a);
[1, 2, 3, 4, 5]
Maple determines that the A and p variables in the partition subprocedure are defined by the parameter and local variable (respectively)
from the outer quicksort procedure and everything works as planned.
The variable A can be passed as a parameter to the partition subprocedure (as in the stand-alone partition procedure). However, A does not
need to be passed because, by using Maple scoping rules, it is available
to the inner procedure.
Creating a Uniform Random Number Generator
If you want to use Maple to simulate physical experiments, you likely
need a random number generator. The uniform distribution is particularly simple: any real number in a given range is equally likely. Thus, a
uniform random number generator is a procedure that returns a random floating-p oint number within a certain range. This section develops
the procedure, uniform, which creates uniform random number generators.
The rand command generates a procedure which returns random
. For example, rand(4..7) generates a procedure that returns ran-
The uniform procedure is similar to rand but returns floating-point
numbers rather than integers. You can use rand to generate random
floating-point numbers between 4 and 7 by multiplying and dividing by
10^Digits.
>
f := rand( 4*10^Digits..7*10^Digits ) / 10^Digits:
>
f();
12 •Chapter 1: Procedures, Variables, and Extending Maple
9482484381
2000000000
The procedure f returns fractions rather than floating-point numbers
so you must compose it with evalf; that is, use evalf(f()). Alternatively, you can perform this operation by using the Maple composition
operator, @.
>
(evalf @ f)();
5.709873593
The following uniform procedure uses evalf to evaluate the constants
in the range specification, r, to floating-point numbers, the map command
to multiply both endpoints of the range by 10^Digits, and round to
round the results to integers.
>
uniform := proc( r::constant..constant )
>
local intrange, f;
>
intrange := map( x -> round(x*10^Digits), evalf(r) );
>
f := rand( intrange );
>
(evalf @ eval(f)) / 10^Digits;
>
end proc:
You can now generate random floating-point numbers between 4
and 7.
The uniform procedure has a serious flaw: uniform uses the current
value of Digits to construct intrange; thus, U depends on the value of
Digits when uniform creates it. On the other hand, the evalf command
within U uses the value of Digits that is current when you invoke U. These
two values are not always identical.
1.1 Nested Procedures• 13
>
U := uniform( cos(2)..sin(1) ):
>
Digits := 15:
>
seq( U(), i=1..8 );
0.440299060400000, 0.366354657300000,
0.0671154810000000, 0.224056281100000,
−0.131130435700000, 0.496918815000000,
0.464843910000000, 0.458498021000000
The proper design choice here is that U should depend only on the
value of Digits when you invoke U. The following version of uniform
accomplishes this by placing the entire computation inside the procedure
that uniform returns.
>
uniform := proc( r::constant..constant )
>
>
proc()
>
>
>
>
>
>
>
end proc:
local intrange, f;
intrange := map( x -> round(x*10^Digits),
evalf(r) );
f := rand( intrange );
evalf( f()/10^Digits );
end proc;
The r within the inner proc is not declared as local or global, so it
becomes the same r as the parameter to the outer proc.
The procedure that uniform generates is now independent of the value
of Digits at the time you invoke uniform.
>
U := uniform( cos(2)..sin(1) ):
>
Digits := 15:
>
seq( U(), i=1..8 );
−0.0785475551312100, 0.129118535641837,
−0.153402552054527, 0.469410168730666,
0.0284411444410350, −0.136140726546643,
−0.156491822876214, 0.0848249080831220
Note:The interface variable displayprecision controls the number of
decimal places to be displayed. The default value is −1, representing full
precision as determined by the Digits environment variable. This simplifies display without introducing round-off error. For more information,
refer to ?interface.
14 •Chapter 1: Procedures, Variables, and Extending Maple
SummaryThis section introduced:
• Rules Maple uses to distinguish global and local variables
• Principal implications of these rules
• Tools available for writing nested procedures
1.2Procedures That Return Procedures
Some of the standard Maple commands return procedures. For example,
rand returns a procedure which in turn produces randomly chosen integers from a specified range. The dsolve function with the type=numeric
option returns a procedure which supplies a numeric estimate of the solution to a differential equation.
You can write procedures that return procedures. This section discusses how values are passed from the outer procedure to the inner procedure.
Conveying Values
The following example demonstrates how locating the roots of a function
by using Newton’s method can be implemented in a procedure.
Creating a Newton Iteration
Use Newton’s method to find the roots of a function.
1. Choose a point on the x-axis that you think might be close to a root.
2. Find the slope of the curve at the point you chose.
3. Draw the tangent to the curve at that point and observe where the
tangent intersects the x-axis. For most functions, this second point is
closer to the real root than your initial guess. To find the root, use
the new point as a new guess and keep drawing tangents and finding
new points.
1.2 Pro cedures That Return Procedures•15
2
1.5
1
0.5
–0.5
0
–1
1234
x1x0
5
x
7
6
8
To find a numerical solution to the equation f (x) = 0, guess an ap-
proximate solution, x0, and then generate a sequence of approximations
using:
1. Newton’s method
2. The following formulation of the previous process
x
k+1
= xk−
f (xk)
f0(xk)
You can implement this algorithm on a computer in a number of ways.
Example 1
The following procedure takes a function and creates a new procedure,
which takes an initial guess and, for that particular function, generates
the next guess. The new procedure does not work for other functions. To
find the roots of a new function, use MakeIteration to generate a new
guess-generating procedure. The unapply command turns an expression
into a procedure.
>
MakeIteration := proc( expr::algebraic, x::name )
>
local iteration;
>
iteration := x - expr/diff(expr, x);
>
unapply(iteration, x);
>
end proc:
The procedure returned by the MakeIteration procedure maps the
name x to the expression assigned to the iteration.
Test the procedure on the expression x − 2
>
expr := x - 2*sqrt(x);
√
x.
16 •Chapter 1: Procedures, Variables, and Extending Maple
expr := x − 2√x
>
Newton := MakeIteration( expr, x);
Newton := x → x −
x − 2√x
1
√
1 −
x
Newton returns the solution, x = 4 after a few iterations.
>
x0 := 2.0;
x0 := 2.0
>
to 4 do x0 := Newton(x0);end do;
x0 := 4.828427124
x0 := 4.032533198
x0 := 4.000065353
x0 := 4.000000000
Example 2
The MakeIteration procedure requires its first argument to be an algebraic expression. You can also write a version of MakeIteration that
works on functions. Since the following MakeIteration procedure recognizes the parameter f as a procedure, you must use the eval command
to evaluate it fully.
>
MakeIteration := proc( f::procedure )
>
(x->x) - eval(f) / D(eval(f));
>
end proc:
>
g := x -> x - cos(x);
g := x → x − cos(x)
>
SirIsaac := MakeIteration( g );
SirIsaac := (x → x) −
x → x − cos(x)
x → 1 + sin(x)
1.2 Pro cedures That Return Procedures•17
Note that SirIsaac is independent of the name g. Thus, you can
change g without breaking SirIsaac. You can find a go od approximate
solution to x − cos(x) = 0 in a few iterations.
>
x0 := 1.0;
x0 := 1.0
>
to 4 do x0 := SirIsaac(x0) end do;
x0 := 0.7503638679
x0 := 0.7391128909
x0 := 0.7390851334
x0 := 0.7390851332
A Shift Operator
Consider the problem of writing a procedure that takes a function, f , as
input and returns a function, g, such that g(x) = f (x + 1). You can write
such a procedure in the following manner.
>
shift := (f::procedure) -> ( x->f(x+1) ):
Try performing a shift on sin(x).
>
shift(sin);
x → sin(x + 1)
Maple lexical scoping rules declare the f within the inner procedure
to be the same f as the parameter within the outer procedure. Therefore,
the shift command works as written.
The previous example of shift works with univariate functions but
it does not work with functions of two or more variables.
>
h := (x,y) -> x*y;
h := (x, y) → x y
>
hh := shift(h);
18 •Chapter 1: Procedures, Variables, and Extending Maple
hh := x → h(x + 1)
>
hh(x,y);
Error, (in h) h uses a 2nd argument, y, which is
missing
Multivariate FunctionsTo modify shift to work with multivariate
functions, rewrite it to accept the additional parameters.
In a procedure, args is the sequence of actual parameters, and
args[2..-1] is the sequence of actual parameters except the first one.
For more information on the selection operation ([ ]), refer to chapter 4
of the
Introductory Programming Guide. It follows that the procedure
x->f(x+1,args[2..-1]) passes all its arguments except the first directly
to f .
The function hh depends on h; if you change h, you implicitly change
hh;
>
h := (x,y,z) -> y*z^2/x;
2
y z
x
2
>
hh(x,y,z);
h := (x, y, z) →
y z
x + 1
1.3 Lo cal Variables and Invoking Procedures •19
1.3Local Variables and Invoking Procedures
Local variables are local to a procedure and to an invocation of that
procedure. Calling a procedure creates and uses new local variables each
time. If you invoke the same procedure twice, the local variables it uses
the second time are distinct from those it used the first time.
Local variables do not necessarily disappear when the procedure exits.
You can write procedures which return a local variable, either explicitly or
implicitly, to the interactive session, where it can exist indefinitely. These
variables are called escaped local variables. This concept can be confusing,
particularly since they can have the same name as global variables, or local
variables which another procedure or a different call to the same procedure
created. You can create many distinct variables with the same name.
Example 1
The following procedure creates a new local variable, a, and then returns
this new variable.
>
make_a := proc()
>
>
>
end proc;
local a;
a;
make_a := proc() local a; a end proc
By using local variables, you can produce displays that Maple would
otherwise simplify. For example, in Maple, a set contains
unique elements.
The following demonstrates that each variable a that make_a returns is
unique.
>
test := { a, a, a };
test := {a}
>
test := test union { make_a() };
test := {a, a}
>
test := test union { ’make_a’()$5 };
test := {a, a, a, a, a, a, a}
20 •Chapter 1: Procedures, Variables, and Extending Maple
This demonstrates that Maple identities consist of more than names.
Important:Independent of the number of variables you create with
the same name, when you type a name in an interactive session, Maple
interprets that name to be a global variable . You can easily find the
global a in the previous set test.
>
seq( evalb(i=a), i=test);
true, false , false, false , false, false , false
Example 2
You can display expressions that Maple would ordinarily simplify automatically. For example, Maple automatically simplifies the expression
a + a to 2a. It is difficult to display the equation a + a = 2a. To display
such an equation, use the procedure make_a from Example 1.
>
a + make_a() = 2*a;
a + a = 2 a
When you type a name in an interactive session, the Maple program
interprets it as the global variable. While this prevents you from using
the assignment
statement to directly assign a value to an escaped local
variable, it does not prevent you from using the assign command. You
must write a Maple expression which extracts the variable. For example,
in the previous equation, you can extract the local variable a by removing
the global a from the left side of the equation.
>
eqn := %;
eqn := a + a = 2 a
>
another_a := remove( x->evalb(x=a), lhs(eqn) );
another_a := a
You can then assign the global name a to this extracted variable and
verify the equation.
1.3 Lo cal Variables and Invoking Procedures •21
>
assign(another_a = a);
>
eqn;
2 a = 2 a
>
evalb(%);
true
Assume FacilityFor complicated expressions, you must use the assume
command to extract the desired variable. You may have encountered this
situation before without realizing it, when you were using the assume
facility to remove an assumption. The assume facility attaches various
definitions to the variable you specify, with one result being that the
name subsequently appears as a local name with an appended tilde. No
relationship exists between the
is displayed as b~, and the
>
assume(b>0);
>
x := b + 1;
local variable b with an assumption, which
global variable name containing a tilde b~.
b~ + 1
x :=
>
subs( ‘b~‘=c, x);
b~ + 1
When you clear the definition of the named variable, the association
between the name and the local name with the tilde is lost, but expressions
created with the local name still contain it.
>
b := evaln(b);
b := b
>
x;
b~ + 1
To reuse the expression, you must either perform a substitution before
removing the assumption or perform some manipulations of the expressions similar to those used for the equation eqn.
22 •Chapter 1: Procedures, Variables, and Extending Maple
Procedure as a Returned Object
An important use for returning local objects arises when the returned
object is a procedure. When you write a procedure, which returns a procedure, you will often find it useful to have the procedure create a variable
that holds information pertinent only to the returned procedure. This allows different procedures (or different invocations of the same procedure)
to pass information among themselves. The following examples illustrate
how different procedures pass information.
Example 3
Creating the Cartesian Product of a Sequence of SetsWhen you pass
a sequence of sets to the procedure, it constructs a new procedure. The
new procedure returns the next term in the Cartesian product each time
you invoke it. Local variables from the outer procedure are used to keep
track of which term to return next.
Cartesian product of a sequence of sets is the set of all lists
The
in which the ith entry is an element of the ith set. Thus, the Cartesian
product of {α, β, γ } and {x, y} is
The number of elements in the Cartesian product of a sequence of sets
grows very rapidly as the number of sets or size of the sets increases. It
therefore requires a large amount of memory to store all the elements of
the Cartesian product.
SolutionYou must write a procedure that returns a new element of the
Cartesian product each time you call it. By calling such a procedure repeatedly, you can process every element in the Cartesian product without
storing all its elements at once.
The following procedure returns the next element of the Cartesian
product of the list of sets s. It uses an array, c, of counters to determine
the next element. For example, c[1]=3 and c[2]=1 correspond to the
third element of the first set and the first element of the second set.
>
s := [ {alpha, beta, gamma}, {x, y} ];
s := [{γ, α, β }, {y, x}]
>
c := array( 1..2, [3, 1] );
c := [3, 1]
1.3 Lo cal Variables and Invoking Procedures •23
>
[ seq( s[j][c[j]], j=1..2 ) ];
[β, y]
Before you call the element procedure you must initialize all the counters to 1, except the first one, which must be 0.
>
c := array( [0, 1] );
c := [0, 1]
In following procedure element, nops(s) is the number of sets and
nops(s[i]) is the number of elements in the ith set. When you have seen
all the elements, the procedure re-initializes the array of counters and
returns FAIL. Therefore, you can repeatedly trace the Cartesian product
by calling element.
>
element := proc(s::list(set), c::array(1, nonnegint))
24 •Chapter 1: Procedures, Variables, and Extending Maple
>
element(s, c);
FAIL
>
element(s, c);
[γ, y]
Example 4
Instead of writing a new procedure for each Cartesian product you study,
you can write a procedure, CartesianProduct, that returns such a procedure. CartesianProduct creates a list, s, of its arguments, which must
be sets, and then initializes the array, c, of counters and defines the subprocedure element. Finally, the element subprocedure is invoked inside
a proc structure.
>
CartesianProduct := proc()
>
local s, c, element;
>
s := [args];
>
if not type(s, list(set)) then
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
end proc:
error "expected a sequence of sets, but received",
args ;
end if;
c := array( [0, 1$(nops(s)-1)] );
element := proc(s::list(set), c::array(1, nonnegint))
Again, you can find all six elements of {α, β , γ} × {x, y}.
>
f := CartesianProduct( {alpha, beta, gamma}, {x,y} );
1.3 Lo cal Variables and Invoking Procedures •25
f := proc()
>
to 7 do f() end do;
element(s, c) end proc
[γ, y]
[α, y]
[β, y]
[γ, x]
[α, x]
[β, x]
FAIL
You can use CartesianProduct to study several products simultane-
ously.
>
g := CartesianProduct( {x, y}, {N, Z, R},
>
g := proc()
{56, 23, 68, 92} );
element(s, c) end proc
The following are the first few elements of {x, y} × {N , Z, R} ×
{56, 23, 68, 92}.
>
to 5 do g() end do;
[y, N , 23]
[x, N, 23]
[y, Z, 23]
[x, Z, 23]
[y, R, 23]
The variables s in f and g are local variables to CartesianProduct,
so they are not shared by different
invocations of CartesianProduct.
Similarly, the variable c in f and g is not shared. You can see that the
two arrays of counters are different by invoking f and g a few more times.
26 •Chapter 1: Procedures, Variables, and Extending Maple
>
to 5 do f(), g() end do;
[γ, y], [x, R, 23]
[α, y], [y, N , 56]
[β, y], [x, N, 56]
[γ, x], [y, Z, 56]
[α, x], [x, Z, 56]
The element procedure in g is also local to CartesianProduct. Therefore, you can change the value of the global variable element without
breaking g.
>
element := 45;
element := 45
>
g();
[y, R, 56]
SummaryThe previous examples demonstrate that local variables can
escape the bounds of the procedures which create them, and that escaped
variables allow you to write procedures which create specialized procedures.
Exercises
1. The procedure that CartesianProduct generates does not work if one
of the sets is empty.
>
f := CartesianProduct( {}, {x,y} );
f := proc()
>
f();
Error, (in element) invalid subscript selector
element(s, c) end proc
1.4 Interactive Input •27
Improve the type-checking in CartesianProduct so that it generates
an informative error message in each such case.
partition of a positive integer, n, is a list of positive integers whose
2. A
sum is n. The same integer can appear several times in the partition
but the order of the integers in the partition is irrelevant. Thus, the
following are all the partitions of 5:
Write a procedure that generates a procedure that returns a new
partition of n each time you call it.
1.4Interactive Input
Normally you pass input to Maple procedures as parameters. Sometimes,
however, you need a procedure to request input directly from the user.
For example, you can write a procedure that tests students on some topic
by generating random problems and verifying the students’ answers. The
input can be the value of a parameter, or the answer to a question such as
whether a parameter is positive. The two commands in Maple for reading
input from the terminal are the readline command and the readstat
command.
Reading Strings from the Terminal
The readline command reads one line of text from a file or the keyboard.
Use the readline command as follows.
readline( filename )
If filename is the special name terminal, then readline reads a line
of text from the keyboard. The readline command returns the text as a
string.
>
s := readline( terminal );
Maplesoft
s := “Maplesoft”
28 •Chapter 1: Procedures, Variables, and Extending Maple
Example 1
The following application prompts the user for an answer to a question.
>
DetermineSign := proc(a::algebraic) local s;
>
>
>
>
>
printf("Is the sign of %a positive?Answer yes or no: ",a);
s := readline(terminal);
evalb( s="yes" or s = "y" );
end proc:
DetermineSign(u-1);
Is the sign of u-1 positive?Answer yes or no: y
true
Information:For more details on the readline command, see Reading Text Lines from a File on page 197.
Reading Expressions from the Terminal
You can write procedures that interpret user input as a Maple expression
rather than a string. The readstat command reads one expression from
the keyboard.
readstat( prompt )
The prompt is an optional string.
>
readstat("Enter degree: ");
Enter degree: n-1;
n − 1
The user input for a readstat command must have a terminating semicolon or colon, or an error is raised.
AdvantagesUnlike the readline command, which only reads one line,
the readstat allows you to break a large expression across multiple lines.
Another advantage of using the readstat command is that if there is
an error in the input, the readstat command automatically repeats the
prompt for user input.
1.4 Interactive Input •29
>
readstat("Enter a number: ");
Enter a number: 5^^8;
syntax error, ‘^‘ unexpected:
5^^8;
^
Enter a number: 5^8;
390625
Example 2
The following is an application of the readstat command that implements an interface to the limit command. The procedure, given the
function f (x), assumes x is the variable if only one variable is present.
Otherwise, the user is asked for the variable and the limit point.
>
GetLimitInput := proc(f::algebraic)
>
local x, a, K;
>
# choose all variables in f
>
K := select(type, indets(f), name);
>
>
if nops(K) = 1 then
>
>
>
>
>
>
>
>
>
>
>
end proc:
x := K[1];
else
x := readstat("Input limit variable: ");
while not type(x, name) do
printf("A variable is required: received %a\n", x);
x := readstat("Please re-input limit variable: ");
end do;
end if;
a := readstat("Input limit point: ");
x = a;
The expression sin(x)/x depends only on one variable, so GetLimitInput
does not prompt for a limit variable.
>
GetLimitInput( sin(x)/x );
Input limit point: 0;
x = 0
30 •Chapter 1: Procedures, Variables, and Extending Maple
In the following output, the user first tries to use the number 1 as the
limit variable. Because 1 is not a name, GetLimitInput requests another
limit variable.
>
GetLimitInput( exp(u*x) );
Input limit variable: 1;
A variable is required: received 1
Please re-input limit variable: x;
Input limit point: infinity;
x = ∞
Information:You can specify a number of options to readstat. For
more information, see Reading Maple Statements on page 204.
Converting Strings to Expressions
For greater control of how and when Maple evaluates user input to a procedure, use the readline command instead of readstat. The readline
command reads the input as a string, and the parse command converts
the string to an expression. The string must represent a complete expression.
>
s := "a*x^2 + 1";
s := “a*x^2 + 1”
>
y := parse( s );
y := a x2+ 1
When you parse the string s you get an expression. In this case, you
get a sum.
>
type(s, string), type(y, ‘+‘);
true, true
1.5 Extending Maple •31
The parse command does not evaluate the expression it returns. You
must use eval to evaluate the expression explicitly. In the following output, the variable a is not evaluted to its value, 2, until you explicitly use
the eval command.
>
a := 2;
a := 2
>
z := parse( s );
z := a x2+ 1
>
eval(z);
2 x2+ 1
Information:For more details about the parse command, see Parsing
Maple Expressions and Statements on page 219.
SummaryThe techniques in this section are very simple, but you can
use them to create useful applications such as Maple tutorials, procedures
that test students, or interactive lessons.
1.5Extending Maple
Although it may be useful to write custom procedures to perform new
tasks, sometimes extending the abilities of Maple commands is most beneficial. This section familiarizes you with:
• Defining custom types and operators
• Modifying how Maple displays expressions
• Extending commands such as simplify and expand.
Defining New Types
If you are using a complicated structured type, it is recommended that
you assign the structured type to a variable of the form ‘type/
name ‘.
32 •Chapter 1: Procedures, Variables, and Extending Maple
Writing the structure once reduces the risk of errors. When you have
defined the variable ‘type/
If the structured type mechanism is not powerful enough, you can
define a new type by assigning a procedure to a variable of the form
‘type/
Maple invokes the procedure ‘type/
name ‘. When you test whether an expression is of type name,
name ‘ on the expression if such a
procedure exists. The procedure should return true or false. The following ‘type/permutation‘ procedure determines if p is a permutation
of the first n positive integers. That is, p should contain exactly one copy
of each integer from 1 through n.
>
‘type/permutation‘ := proc(p)
>
local i;
>
type(p,list) and { op(p) } = { seq(i, i=1..nops(p)) };
>
end proc:
>
type( [1,5,2,3], permutation );
false
>
type( [1,4,2,3], permutation );
true
The type-testing procedure can accept more than one parameter.
When you test if an expression,
expr, has type name (parameters), then
Maple invokes
‘type/name ‘( expr, parameters )
if such a procedure exists. The following ‘type/LINEAR‘ procedure determines if f is a polynomial in V of degree 1.
1.5 Extending Maple •33
>
‘type/LINEAR‘ := proc(f, V::name)
>
type( f, polynom(anything, V) ) and degree(f, V) = 1;
>
end proc:
>
type( a*x+b, LINEAR(x) );
true
>
type( x^2, LINEAR(x) );
false
>
type( a, LINEAR(x) );
false
Exercises
1. Modify the ‘type/LINEAR‘ procedure so that you can use it to test
if an expression is linear in a set of variables. For example, x + ay + 1
is linear in both x and y, but xy + a + 1 is not.
2. Define the type POLYNOM(
a polynomial in
X where X is a name, a list of names, or a set of
X ) which tests if an algebraic expression is
names.
Neutral Operators
The Maple software recognizes many operators, for example +, *, ^, and,
not, and union. These operators have special meaning to Maple. The
operators can represent:
• Algebraic operations, such as addition or multiplication
• Logical operations
• Operations performed on sets
Maple also has a special class of operators, the
on which it does not impose any meaning. Instead, Maple allows
to define the meaning of any neutral operator. The name of a neutral
operator begins with the ampersand character (&).
>
7 &^ 8 &^ 9;
neutral operators ,
you
34 •Chapter 1: Procedures, Variables, and Extending Maple
(7 &^ 8) &^ 9
>
evalb( 7 &^ 8 = 8 &^ 7 );
false
>
evalb( (7&^8)&^9 = 7&^(8&^9) );
false
Internally, Maple represents neutral operators as procedure calls.
Thus, 7&^8 is a convenient way of writing &^(7,8).
>
&^(7, 8);
7 &^ 8
Maple uses the infix notation, in which the operator is placed between
the operands, only if the neutral operator has exactly two arguments.
>
&^(4),&^(5, 6), &^(7, 8, 9);
&^(4), 5 &^ 6, &^(7, 8, 9)
Information:For more information on naming conventions for neutral
operators, refer to chapter 3 of the
Introductory Programming Guide.
Example 1
You can define the actions of a neutral operator by assigning a procedure to its name. The following example implements the Hamiltonians by
assigning a neutral operator to a procedure that multiplies two Hamiltonians.
Mathematical PremiseThe
complex numbers in the same way the complex numbers extend the real
numbers. Each Hamiltonian has the form a + bi + cj + dk where a, b,
c, and d are real numbers. The special symbols i, j, and k satisfy the
following multiplication rules: i2= −1, j2= −1, k2= −1, ij = k,
ji = −k, ik = −j, ki = j , j k = i, and kj = −i.
Hamiltonians or Quaternions extend the
1.5 Extending Maple •35
The following ‘&^‘ procedure uses I, J , and K as the three special
symbols. However, I is implemented as the
complex imaginary unit in
Maple. Therefore, you should assign another letter to represent the imaginary unit by using the interface function. For more information, refer
to ?interface.
>
interface(imaginaryunit=j);
j
You can multiply many types of expressions by using ‘&^‘, making it
convenient to define a new type, Hamiltonian, by assigning a structured
type to the name ‘type/Hamiltonian‘.
>
‘type/Hamiltonian‘ := { ‘+‘, ‘*‘, name, realcons,
>
specfunc(anything, ‘&^‘) };
type/Hamiltonian :=
{∗, +,
realcons, name, specfunc(anything, &^)}
The ‘&^‘ procedure multiplies the two Hamiltonians, x and y. If either
x or y is a real number or variable, then their product is the usual product
denoted by * in Maple. If x or y is a sum, ‘&^‘ maps the product onto the
sum; that is, ‘&^‘ applies the distributive laws: x(u + v ) = xu + xv and
(u + v)x = ux + vx. If x or y is a product, ‘&^‘ extracts any real factors.
You must take special care to avoid infinite recursion when x or y is a
product that does not contain real factors. If none of the multiplication
rules apply, ‘&^‘ returns the product unevaluated.
>
‘&^‘ := proc( x::Hamiltonian, y::Hamiltonian )
>
local Real, unReal, isReal;
>
isReal := z -> evalb( is(z, real) = true );
>
>
if isReal(x) or isReal(y) then
>
>
>
>
>
>
>
>
>
>
>
>
>
x * y;
elif type(x, ‘+‘) then
# x is a sum, u+v, so x&^y = u&^y + v&^y.
map(‘&^‘, x, y);
elif type(y, ‘+‘) then
# y is a sum, u+v, so x&^y = x&^u + x&^v.
map2(‘&^‘, x, y);
elif type(x, ‘*‘) then
# Pick out the real factors of x.
Real, unReal := selectremove(isReal, x);
36 •Chapter 1: Procedures, Variables, and Extending Maple
Since ‘&^‘ is a neutral operator, you can write products of Hamiltonians using &^ as the multiplication symbol.
>
(1 + 2*I + 3*J + 4*K) &^ (5 + 3*I - 7*J);
20 + 41 I + 20 J − 3 K
>
(5 + 3*I - 7*J) &^ (1 + 2*I + 3*J + 4*K);
20 − 15 I − 4 J + 43 K
1.5 Extending Maple •37
>
56 &^ I;
56 I
In the following example, a is an unknown Hamiltonian until you enter
the assumption that a is an unknown real number.
>
a &^ J;
a &^ J
>
assume(a, real);
>
a &^ J;
a~ J
Exercise
1. The inverse of a general Hamiltonian, a +bi + cj + dk, is (a − bi − cj −
dk)/(a2+ b2+ c2+ d2). You can demonstrate this fact by assuming
that a, b, c, and d are real and define a general Hamiltonian, h.
>
assume(a, real); assume(b, real);
>
assume(c, real); assume(d, real);
>
h := a + b*I + c*J + d*K;
a~ + b~ I + c~ J + d ~ K
h :=
By the formula above, the following should be the inverse of h.
>
hinv := (a-b*I-c*J-d*K) / (a^2+b^2+c^2+d^2);
hinv :=
a~ − b~ I − c~ J − d ~ K
a~2+ b ~2+ c ~2+ d ~
2
Check that h &^ hinv and hinv &^ h simplify to 1.
>
h &^ hinv;
38 •Chapter 1: Procedures, Variables, and Extending Maple
a~ (a~ − b~ I − c ~ J − d ~ K )
%1
b~ (I a~ + b~ − c~ K + d ~ J )
>
simplify(%);
+
c~ (J a~ + b~ K + c ~ − d ~ I )
+
d ~ (K a~ − b~ J + c~ I + d ~)
+
%1 :=
a~2+ b ~2+ c ~2+ d ~
%1
%1
%1
2
1
>
hinv &^ h;
a~ (a~ − b~ I − c ~ J − d ~ K )
%1
a~ b~ I + b~2+ b ~ c ~ K − b~ d ~ J
>
simplify(%);
+
a~ c ~ J − b~ c ~ K + c ~2+ c ~ d ~ I
+
a~ d ~ K + b~ d ~ J − c~ d ~ I + d ~
+
%1 :=
a~2+ b ~2+ c ~2+ d ~
%1
%1
2
%1
2
1
Write a procedure, ‘&/‘, that computes the inverse of a Hamiltonian.
It is recommended that you implement the following rules.
&/( &/x ) = x,&/(x&^y) = (&/y) &^ (&/x),
x &^ (&/x) = 1 = (&/x) &^ x.
1.5 Extending Maple •39
Extending Commands
If you introduce custom data structures, there are no manipulation rules
for them. In most cases, you write special-purpose procedures that manipulate new data structures. However, sometimes extending the capabilities
of one or more of the Maple built-in commands is easier than developing new data structures and special-purpose procedures. You can extend
several Maple commands, among them expand, simplify, diff, series,
and evalf.
Extending the Diff CommandYou can represent a polynomial anun+
n−1
a
u
n−1
POLYNOM( u, a_0, a_1, ..., a_n )
You can then extend the diff command so that you can differentiate
polynomials represented in that way. If you write a procedure with a
name of the form ‘diff/
calls to
with respect to
+ · · · + a1u + a0by using the data structure
F ‘ then diff invokes it on any unevaluated
F. Specifically, if you use diff to differentiate F(arguments )
x, then diff invokes ‘diff/F ‘ as follows.
‘diff/F ‘( arguments, x )
The following procedure differentiates a polynomial in u with constant
coefficients with respect to x.
40 •Chapter 1: Procedures, Variables, and Extending Maple
Extending the simplify CommandThe implementation of the Hamiltonians in this section 1.5 does not include the associative rule for multiplication of Hamiltonians, that is (xy )z = x(yz). Sometimes, using associativity simplifies a result. Recall that I here is
not the complex imaginary
unit, but rather, one of the special symbols I , J , and K that are part of
the definition of the Hamiltonians.
>
x &^ I &^ J;
(x &^ I) &^ J
>
x &^ ( I &^ J );
x &^ K
You can extend the simplify command so that it applies the associative law to unevaluated products of Hamiltonians. If you write a
procedure with a name of the form ‘simplify/
vokes it on any unevaluated function calls to
F ‘, then simplify in-
F. Thus, you must write a
procedure ‘simplify/&^‘ that applies the associative law to Hamiltonians.
The following procedure uses the typematch command to determine
if its argument is of the form (a&^b)&^c and, if so, it selects the a, b,
and c.
The userinfo CommandYou can give the user details about procedure
simplifications using the userinfo command. The ‘simplify/&^‘ procedure prints an informative message if you set infolevel[simplify] or
infolevel[all] to greater than or equal to least 2.
>
‘simplify/&^‘ := proc( x )
>
local a, b, c;
>
if typematch( x,
>
>
>
>
>
>
>
>
end proc:
else
end if;
’‘&^‘’( ’‘&^‘’( a::anything, b::anything ),
c::anything ) ) then
userinfo(2, simplify, "applying the associative law");
a &^ ( b &^ c );
x;
Applying the associative law simplifies some products of Hamiltoni-
ans.
>
x &^ I &^ J &^ K;
((x &^ I) &^ J ) &^ K
>
simplify(%);
−x
If you set infolevel[simplify] to a sufficiently large value, Maple
prints information on the methods used by simplify while attempting to
simplify the expression.
>
infolevel[simplify] := 5;
>
w &^ x &^ y &^ z;
infolevel
simplify
:= 5
((w &^ x) &^ y) &^ z
>
simplify(%);
simplify/&^:"applying the associative law"
simplify/&^:"applying the associative law"
42 •Chapter 1: Procedures, Variables, and Extending Maple
w &^ ((x &^ y) &^ z)
Information:For details on how to extend these commands, refer to
?expand, ?series, and ?evalf. For information on extending the evalf
command, see also 4.4 Extending the evalf Command.
1.6Conclusion
Procedures which return procedures and local variables are fundamental
to advanced programming. Interactive input and extending Maple are also
important topics in advanced programming.
2Programming with
Modules
Procedures allow you to associate a sequence of commands with a single
command. Similarly, modules allow you to associate related procedures
and data.
Modules
This chapter describes Maple modules. Modules are a type of Maple expression (like numbers, equations, and procedures), that enable you to
write generic algorithms, create packages, or use Pascal-style records in
programs.
The use of modules satifies four important software engineering concepts.
• Encapsulation
• Packages
• Object Mo deling
• Generic Programming
Encapsulationguarantees that an abstraction is used only according to
its specified interface. You can write significant software systems that are
transportable and reusable and that offer clean, well-defined user interfaces. This makes code easier to maintain and understand—important
properties for large software systems.
Packagesare a vehicle for bundling Maple procedures related to a problem domain. Much of the functionality of the standard Maple library
resides in packages.
43
44 •Chapter 2: Programming with Modules
Objectsare easily represented using modules. In software engineering
or object-oriented programming, an object is defined as something that
has both state and behavior. You compute with objects by sending them
messages, to which they respond by performing services.
Generic Programsaccept objects that possess specific properties or behaviors. The underlying representation of the object is transparent to
generic programs.
Examples
For better understanding, it is helpful to examine a small module.
Example 1: Simple ModuleWhen Maple evaluates the right side of
the assignment to TempGenerator, it creates a
definition
>
>
>
>
>
>
>
>
>
>
>
that begins with module()... and ends with end module.
TempGenerator := module()
description "generator for temporary symbols";
export gentemp;
local count;
count := 0;
gentemp := proc()
count := 1 + count;
‘tools/gensym‘( T || count )
end proc;
end module;
module using the module
TempGenerator := module()
local count;
export gentemp;
description “generator for temporary symbols”;
end module
Example SummaryThe module definition resembles a procedure definition. The main differences are the use of the keyword module instead
of proc (and the corresponding terminator) and the export declaration
following the description string.
Example 2: ProcedureIn the following example, the previous module
is written using only procedures.
>
TempGeneratorProc := proc()
>
description "generator for temporary symbols";
>
local count, gentemp;
>
count := 0;
• 45
>
>
>
>
>
>
end proc:
gentemp := proc()
count := 1 + count;
‘tools/gensym‘( T || count )
end proc;
eval( gentemp, 1 )
You can assign the procedure returned by TempGeneratorProc, and
then use it to generate temporary symbols.
>
f := TempGeneratorProc();
f := proc()
count := 1 + count ; ‘tools/gensym‘(T ||count )
end proc
>
f();
T1
>
f();
T2
Module Versus Procedure
The module TempGenerator and the procedure TempGeneratorProc are
similar.
In the procedure version, the local variable gentemp is assigned a procedure that references another local variable count; the value of gentemp
is returned by the procedure to its caller. The module version of the
generator behaves similarly. Its structure differs: its gentemp variable is
declared as an
In both versions of the generator, the variables count and gentempare local variables. The significant difference here is that, in the module
version, one of those local variables is
is available outside the scope of the structure in which it was created.
Special syntax is used access exported local variables. For example, to
call the exported variable gentemp of the module, enter
>
TempGenerator:-gentemp();
export, not a local, and there is no explicit return.
exported.This means that it
T1
46 •Chapter 2: Programming with Modules
using the member selection operator :-. A module definition returns a
data structure (a module) that contains all of its exported local variables.
Accessing Module Exports
The use statement allows you to access module exp orts.
>
use TempGenerator in
>
>
>
>
Within the body of a use statement, the exported local variables of
the module that appears after the use keyword can be accessed directly,
without using the member selection operator :-.
gentemp();
gentemp();
gentemp();
end use;
T2
T3
T4
In This Chapter
This chapter provides many example modules. Some examples are very
simple, designed to illustrate a specific point. Others are more substantial.
Many of the nontrivial examples are available as Maple source code in the
samples directory of the Maple installation. You can load them into the
private Maple library and experiment with them. You can modify, extend,
and improve these code samples, and use them in custom programs.
The following topics are covered in this chapter.
• Syntax and Semantics
• Using Modules as Records or Structures
• Using Modules To Write Maple Packages
• The use Statement
• Modeling Ob jects
• Interfaces and Implementations
2.1 Syntax and Semantics •47
2.1Syntax and Semantics
The syntax of module definitions is very similar to that of procedures,
given in chapter 6 of the
example of a simple module definition.
>
module()
>
export e1;
>
local a, b;
>
>
a := 2;
>
b := 3;
>
e1 := x -> a^x/b^x;
>
end module:
Evaluating this expression results in a module with one export, e1, and
two local variables, a and b.
A template for a module definition looks like:
module()
local
export
global
options
L ;
E ;
G ;
O ;
description
B
end module
Introductory Programming Guide. Here is an
D ;
The simplest valid module definition is
>
module() end;
module() end module
This module definition does
not have: exported variables, locals, references, global variables, or a body of statements. The module to which
this evaluates is not very useful.
The Module Definition
Every module definition begins with the keyword module, followed by
an empty pair of parentheses. Following that is an optional declaration
section and the module body. The keyword combination end module (or
just end) terminates a module definition.
48 •Chapter 2: Programming with Modules
The Module Body
The body of a module definition consists of the following.
• Zero or more Maple statements. The bo dy is executed when the module definition is evaluated, producing a module.
• A number of assignment statements that give values to the exported
names of the module.
The body of a module definition can also contain:
• Assignments to local variables, and performance of arbitrary computations.
• A return statement, but cannot contain a break or next statement
outside a loop. Executing a return statement terminates the execution of the body of the module definition.
Module Parameters
Module definitions begin with the Maple keyword module, followed by an
(empty) pair of parentheses. This is similar to the parentheses that follow
the proc keyword in a procedure definition. Unlike procedures, however,
module definitions do not have explicit parameters because modules are
not called (or invoked) with arguments.
Implicit ParametersEvery module definition has an
called thismodule. Within the body of a module definition, this special
name evaluates to the module in which it occurs. This allows you to refer
to a module within its own definition (before the result of evaluating it
has been assigned to a name).
All procedure definitions can reference the implicit parameters proc-
name, args, and nargs. Module definitions cannot reference these im-
plicit parameters. Additionally, the difference between thismodule and
procname is that procname evaluates to a
evaluates to the module expression itself. This is because the invocation
phase of evaluating a module definition is part of its normal evaluation,
and it occurs immediately. Procedures, on the other hand, are not invoked
until called with arguments. Normally, at least one name for a procedure
is known by the time it is called; this is not the case for modules.
name, while thismodule
implicit parameter
Named Modules
An optional symbol may appear after the module keyword in a module definition. Modules created with this variant on the syntax are called
2.1 Syntax and Semantics •49
named modules . Semantically, named modules are nearly identical to
normal modules, but the exported members of named modules are printed
differently, allowing the module from which it was exported to be identified visually.
>
NormalModule := module() export e; end;
NormalModule := module() export e; end module
>
NormalModule:-e;
e
Here, the symbol (the name of the module) after the module keyword
is NamedModule.
>
module NamedModule() export e; end module;
module
>
NamedModule:-e;
NamedModule () export e; end module
NamedModule : −e
When the definition of a named module is evaluated, the name (which
appears immediately after the module keyword) is assigned the module
as its value, and the name is protected. Therefore, a named module can,
ordinarily, be created only once. For example, an attempt to execute the
same named module definition yields an error.
>
module NamedModule() export e; end module;
Error, (in NamedModule) attempting to assign to
‘NamedModule‘ which is protected
Executing the normal module definition again creates a
new instance of the module, but does not result in an error. (It simply reassigns
the variable NormalModule to the new module instance.)
>
NormalModule := module() export e; end;
NormalModule := module() export e; end module
50 •Chapter 2: Programming with Modules
ImportantDo not assign a named module to another variable.
>
SomeName := eval( NamedModule );
SomeName :=
module NamedModule () export e; end module
>
SomeName:-e;
NamedModule : −e
Exports of named modules are printed using the
distinguished name
that was given to the module when it was created, regardless of whether
it has been assigned to another name.
Whether a module has a name also affects the reporting of errors
that occur during its evaluation. When the second attempt to evaluate
the named module definition above failed, the error message reported the
location of the error by name. By contrast, when an error occurs during
the evaluation of a normal module definition, the name unknown is used
instead.
This differs from procedure error reporting. Maple cannot report
the name of a normal module (that is, the name of the variable to which
the module is assigned), because the evaluation of the right side of an
assignment occurs
before the assignment to the name takes place. So the
error occurs before any association between a variable and the module
has occurred.
Declarations
The declarations section of the module must appear immediately after
the parentheses. All statements in the declarations section are optional,
but at most one of each kind may appear. Most module declarations are
the same as those for procedures.
Description StringsProvide a brief description outlining the purpose
and function of any module you write. It is valuable to other users who
read your code. Include an overview after the description keyword, just
as you would in a procedure definition.
2.1 Syntax and Semantics •51
>
Hello := module()
>
>
>
>
>
>
description "my first module";
export say;
say := proc()
print( "HELLO WORLD" )
end proc;
end module:
When the module is printed, its description string is displayed.
>
eval( Hello );
module()
export
say;
description “my first module”;
end module
The export declaration is explained later in this chapter.
Global VariablesGlobal variables referenced within a module definition
should be declared with the global declaration. Following the keyword
global is a sequence of one or more symbols. These symbols are bound
to their global instances. In certain cases you must declare a name as a
global variable to prevent implicit scoping rules from making it local.
>
Hello := module()
>
>
>
>
>
>
>
export say;
global message;
say := proc()
message := "HELLO WORLD!"
end proc;
end module:
message;
>
Hello:-say();
>
message;
message
“HELLO WORLD!”
“HELLO WORLD!”
52 •Chapter 2: Programming with Modules
Local Variables You can refer to variables that are local to the module
definition by using the local declaration. Its format is the same as for
procedures. Here is a variant on the previous Hello module which uses a
local variable.
>
Hello := module()
>
>
>
>
>
>
>
local loc;
export say;
loc := "HELLO WORLD!";
say := proc()
print( loc )
end proc;
end module:
Local variables are not visible outside the definition of the module
in which they occur. They are private to the module, and are exactly
analogous to local variables of procedures.
A local variable in a module (or procedure) is a distinct object from
a global variable with the same name. Local variables are normally shortlived variables; the normal lifetime of a local variable is the execution time
of the body of code (a module or procedure body) to which it is local.
(Local variables may persist once execution of the scope in which they occur has completed, but they are normally inaccessable and will eventually
be recycled by the Maple automatic storage management system.)
Exported Local Variables
Procedures and modules both support local variables. Only modules sup-
exported local variables, often referred to simply as exports.
port
Module exports are declared using the export declaration. It begins
with the keyword export, after which follows a (nonempty) sequence of
symbols. A name is never exported implicitly; exports
The result of evaluating a module definition is a mo dule. You can
view a module as a collection of its exports, which are also referred to
as members of the module. These are simply names that can (but need
not) be assigned values. You can establish initial values for the exports
by assigning to them in the body of the module definition.
The word export is short for exported local variable. In most respects,
a module export is a local variable (such as those declared via the local
declaration.) The crucial difference is that you can access the exported
local variables of a module after it has been created.
To access an export of a module, use the :- member selection operator. Its general syntax is:
must be declared.
2.1 Syntax and Semantics •53
modexpr :- membername
Here, modexpr must be an expression that evaluates to a module, and
membername must be the name of an export of the module to which
modexpr evaluates. Anything else signals an exception. You cannot ac-
cess local variables of an instantiated module by using this syntax.
Local variables of a procedure are created when the procedure is called
(or invoked). Normally, the locals persist only during the execution of the
statements that form the body of the procedure. Sometimes, however,
local variables persist beyond the procedure activation that instantiated
them. For example:
>
gen := proc()
>
>
>
>
>
>
local s, p;
s := 2;
p := x -> s * x;
p
end proc:
g := gen();
g := p
>
g( 3 );
6
The local variable s of gen persists after gen has returned. It is captured in the closure of the procedure p, whose name is returned by gen.
Thus, both local variables p and s of gen escape, but in different ways.
The local name p is accessible because it is the assigned value of the
global variable g. However, there is no way to refer to s once gen has
returned. No Maple syntax exists for that purpose. The member selection
operator :- provides a syntax for referencing certain local variables of
modules–those declared as exports.
The most recent Hello example has one export, named say. In this
case, say is assigned a procedure. To call it, enter
>
Hello:-say();
“HELLO WORLD!”
The following expression raises an exception, because the name
noSuchModule is not assigned a module expression.
54 •Chapter 2: Programming with Modules
>
noSuchModule:-e;
Error, ‘noSuchModule‘ does not evaluate to a module
Here, a module expression is assigned to the name m, and the
member selection expression m:-e evaluates to the value of the exported
variable e of m.
>
m := module() export e; e := 2 end module:
>
m:-e;
2
Since m does not export a variable named noSuchExport, the following
expression raises an exception.
>
m:-noSuchExport;
Error, module does not export ‘noSuchExport‘
ImportantThe following module exports an unassigned name. This
illustrates the importance of distinguishing module exports from global
variables.
>
m := module() export e; end:
References to the exported name e in m evaluate to the name e.
>
m:-e;
e
Note, however, that this is a
local name e, not the global instance of
the name.
>
evalb( e = m:-e );
false
The first e in the previous expression refers to the global e, while the
expression m:-e evaluates to the e that is local to the module m. This
distinction between a global and export of the same name is useful. For
example, you can create a module with an export sin. Assigning a value
to the export sin does not affect the protected global name sin.
2.1 Syntax and Semantics •55
The exports ProcedureYou can determine the names of the exports
of a module by using the exports procedure.
>
exports( Hello );
say
>
exports( NormalModule );
e
This returns the global instances of the export names.
>
exports( m );
e
>
evalb( % = e );
true
You can also obtain the local instances of those names by passing the
option instance.
>
exports( m, ’instance’ );
e
>
evalb( % = e );
false
>
evalb( %% = m:-e );
true
For this reason, you cannot have the same name declared both as a
local and an export.
>
module() export e; local e; end;
Error, export and local ‘e‘ have the same name
56 •Chapter 2: Programming with Modules
(The declared exports and locals actually form a partition of the
names that are local to a module.)
The member ProcedureYou have already seen the built-in procedure
member that is used to test for membership in a set or list.
>
member( 4, { 1, 2, 3 } );
false
This procedure can be used for membership tests in modules as well.
>
member( say, Hello );
true
>
member( cry, Hello );
false
The first argument is a (global) name whose membership is to be
tested, and the second argument is a module. It returns the value true if
the module has an export whose name is the same as the first argument.
The procedure member also has a three argument form that can be
used with lists to determine the (first) position at which an item occurs.
>
member( b, [ a, b, c ], ’pos’ );
true
The name pos is now assigned the value 2 because b occurs at the
second position of the list [ a, b, c].
>
pos;
2
When used with modules, the third argument is assigned the local
instance
of the name whose membership is being tested, provided that
the return value is true.
>
member( say, Hello, ’which’ );
>
which;
>
eval( which );
2.1 Syntax and Semantics •57
true
say
proc() print(
loc) end proc
If the return value from member is false, then the name remains
unassigned (or maintains its previously assigned value).
>
unassign( ’which’ ):
>
member( cry, Hello, ’which’ );
false
>
eval( which );
which
Module Options
As with procedures, a module definition may contain options. The options available for mo dules are different from those for procedures. Only
the options trace, and ‘Copyright...‘ are common to procedures and
modules. The following four options have a predefined meaning for mo dules: load, unload, package, and record.
The load and unload OptionsThe module initialization option is
load= pname where pname is the name of a procedure in the declared
exports or locals of the module. If this option is present, then the procedure is called when the module is read from the Maple repository in
which it is found. The unload = pname option specifies the name of a
local or exported procedure of the module that is called when the module
is destroyed. A module is destroyed either when it is no longer accessible
and is garbage collected, or when Maple exits.
There is a situation that can arise wherein a module is no longer
accessible, and hence subject to garbage collection before the unload=
procedure is executed, but becomes accessible again during the execution
58 •Chapter 2: Programming with Modules
of that procedure. In that case, the module is
not garbage collected. When
it eventually is garbage collected or Maple exits, the unload= procedure
not executed again. The load= and unload= procedures are called
is
with no arguments.
The package OptionModules with the option package represent Maple
packages. The exports of a module created with the package option are
automatically protected.
The record OptionThe record option is used to identify records.
Records are produced by the Record constructor and are represented
using modules.
Implicit Scoping Rules
The bindings of names that appear within a module definition are determined when the module definition is simplified. Module definitions are
subject to the same implicit scoping rules that procedure definitions are.
Under no circumstances is a name ever implicitly determined to be exported by a module; implicitly scoped names can resolve only to locals or
globals.
Lexical Scoping Rules
Module definitions, along with procedure definitions, obey standard lexical scoping rules. Modules may be nested, in the sense that a module
may have any of its exports assigned to a module whose definition occurs
within the body of the outer module.
Here is a simple example of a submodule.
>
m := module()
>
>
>
>
>
>
>
>
The global name m is assigned a module that exports the name s. Within
the body of m, the export s is assigned a module that exports the name
e. As such, s is a
illustrates a nontrivial use of submodules.
Modules and procedures can be mutually nested to an arbitrary depth.
The rules for the visibility of lo cal variables (including exported locals of
export s;
s := module()
export e;
e := proc()
print( "HELLO WORLD!" )
end proc;
end module
end module:
submodule of m. The Shapes package, described later,
2.1 Syntax and Semantics •59
modules) and procedure parameters are the same as the rules for nested
procedures.
Parameterized ModulesModules do not take explicit parameters. You
can write a generic module that could be specialized by providing one or
more parameters.
For example, here is a module for arithmetic modulo 6.
>
z6 := module()
>
>
>
>
>
export add, mul;
add := ( a, b ) -> a + b mod 6;
mul := ( a, b)-> a * b mod 6;
end module:
z6:-add( 5, 4);
3
>
z6:-mul( 2, 3);
0
You can write a
generic module for arithmetic modulo any positive
integer n, and then specialize it for any integer that you need. This is
possible as a result of the standard lexical scoping rules. You must write
a
constructor procedure for the module that accepts the value of n as
an argument. Here is a generic version of the z6 example.
>
MakeZn := proc( n::posint )
>
>
>
>
>
>
module()
export add, mul;
add := ( a, b ) -> a + b mod n;
mul := ( a, b ) -> a * b mod n;
end module
end proc:
To generate a module that does arithmetic modulo 7, call the constructor
MakeZn with the number 7 as its argument.
>
z7 := MakeZn( 7 );
z7 := module() export add , mul ; end module
>
z7:-add( 3, 4 );
0
60 •Chapter 2: Programming with Modules
Modules and Types
Two Maple types are associated with modules. First, the name module
is a type name. Naturally, an expression is of type module only if it is a
module. When used as a type name, the name module must be enclosed
in name quotes (‘).
>
type( module() end, ’‘module‘’ );
true
>
type( LinearAlgebra, ’‘module‘’ );
true
Secondly, a type called moduledefinition identifies expressions that
are module definitions. In the previous example, the module definition
>
module() end:
was evaluated before being passed to type, so the expression that was
tested was not the definition, but the module to which it evaluates. You
must use unevaluation quotes (’) to delay the evaluation of a module
definition.
>
type( ’module() end’, ’moduledefinition’ );
true
Other important type tests satisfied by modules are the types atomic
and last_name_eval.
>
type( module() end, ’atomic’ );
true
The procedure map has no effect on modules; they pass through unchanged.
>
map( print, module() export a, b, c; end );
module() export a, b, c; end module
Modules also follow last name evaluation rules. For more information
on last name evaluation rules, refer to ?last_name_eval.
2.1 Syntax and Semantics •61
>
m := module() end:
>
m;
m
>
type( m, ’last_name_eval’ );
true
Although type module is a surface type, it acts also as a structured
type. Parameters passed as arguments to the unevaluated name module
are taken to be the names of exports. For example, the module
>
m := module() export a, b; end:
has the structured module type ‘module‘( a, b ):
>
type( m, ’‘module‘( a, b )’ );
true
It also has type type ‘module‘( a )
>
type( m, ’‘module‘( a )’ );
true
because any module that exports symbols a and b is a module that
exports the symbol a.
Example: A Symbolic Differentiator
This section illustrates the various module concepts through a symbolic
differentiator example. Since Maple provides a built-in differentiator diff,
the example symbolic differentiator is named differentiate. Its (final)
implementation is in the module DiffImpl (later in this chapter), which
holds all the local state for the program. Much of the code for the differentiator is designed to implement either a standard rule (such as the
rule that the derivative of a sum is the sum of the derivatives of the summands), or special case rules for mathematical functions such as sin and
exp. The example differentiator handles only real valued functions of a
single real variable.
62 •Chapter 2: Programming with Modules
The following example shows several steps in the development of the
module, from a very simple first try to the final, fully functional program.
The final form of the differentiator is a good illustration of a very common
Maple design pattern. This pattern arises when you have a single top-level
routine that dispatches a number of subroutines to handle special cases
using special purpose algorithms.
The First AttemptThis initial example presents the differentiator as
an ordinary procedure, not a module.
>
differentiate := proc( expr, var )
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
local a, b;
if type( expr, ’constant’ ) then
0
elif expr = var then
1
elif type( expr, ’‘+‘’ ) then
map( procname, args )
elif type( expr, ’‘^‘’ ) then
a, b := op( expr );
if a = var and not has( b, var ) then
b * a ^ ( b - 1 )
else
’procname( args )’
end if
elif type( expr, ’‘*‘’ ) then
a, b := op( 1, expr ), subsop( 1 = 1, expr );
procname( a, var ) * b + a * procname( b, var )
else
’procname( args )’
end if
end proc:
Trivial cases are handled first: The derivative of a constant expression is
equal to 0, and the derivative of the variable with respect to which we are
differentiating is equal to 1. The additivity of the derivative operator is
expressed by mapping the procedure over sums, using the command
>
map( procname, args );
This is commonly used to map a procedure over its first argument,
passing along all the remaining arguments. Only the simple case of powers
of the differentiation variable is handled so far, provided also that the
power is independent of the differentiation variable. The product rule for
derivatives is expressed by splitting expressions of type product into two
pieces:
• the first factor in the product, and
• the product of all the remaining factors.
2.1 Syntax and Semantics •63
This is achieved by the double assignment of
>
a, b := op( 1, expr ), subsop( 1 = 1, expr );
so the input expression expr is expressed as expr = a * b. The standard technique of returning unevaluated is used so that computation can
proceed symbolically on expressions that the procedure is unable to differentiate.
This first example is simple, but it is already able to handle polynomials with numeric coefficients.
>
differentiate( 2 - x + x^2 + 3*x^9, x );
−1 + 2 x + 27 x
8
However, it fails on expressions containing calls to standard mathematical functions.
>
differentiate( sin( x ), x );
differentiate(sin(x), x)
It is also unable to deal successfully with symbolic coefficients.
>
differentiate( a*x^2 + b*x + c, x );
differentiate(a, x) x2+ 2 a x + differentiate(b, x) x + b
+ differentiate(c, x)
Adding Missing FunctionalityTo add the missing functionality, add a
case for expressions of type function.
>
differentiate := proc( expr, var )
>
>
>
>
>
>
>
>
>
>
>
>
>
>
local a, b;
if not has( expr, var ) then
0
elif expr = var then
1
elif type( expr, ’‘+‘’ ) then
map( procname, args )
elif type( expr, ’‘^‘’ ) then
a, b := op( expr );
if not has( b, var ) then
b * a ^ ( b - 1 ) * procname( a, var )
else
’procname( args )’
64 •Chapter 2: Programming with Modules
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
end proc:
end if
elif type( expr, ’‘*‘’ ) then
a, b := op( 1, expr ), subsop( 1 = 1, expr );
procname( a, var ) * b + a * procname( b, var )
elif type( expr, ’function’ ) and nops( expr ) = 1 then
# functions of a single variable; chain rule
b := op( 0, expr ); # the name of the function
a := op( 1, expr ); # the argument
if b = ’sin’ then
cos( a ) * procname( a, var )
elif b = ’cos’ then
-sin( a ) * procname( a, var )
elif b = ’exp’ then
exp( a ) * procname( a, var )
elif b = ’ln’ then
( 1 / a ) * procname( a, var )
else
’procname( args )’
end if
else
’procname( args )’
end if
This uses the chain rule to compute the derivatives of calls to known
functions.
>
differentiate( sin( x ) + cos( exp( x ) ), x );
cos(x) − sin(ex) e
>
differentiate( sin( x^2 ) + cos( x^2 ), x );
x
2 cos(x2) x − 2 sin(x2) x
>
differentiate( sin( x )^2 + cos( x )^3, x );
2 sin(x) cos(x) − 3 cos(x)2sin(x)
At the same time, this has also improved the handling of expressions
independent of the variable of differentiation.
>
differentiate( a*x^2 + b*x + c, x );
2 a x + b
2.1 Syntax and Semantics •65
This is effected by using the expression has( expr, var ) instead of
the weaker test type( expr, ’constant’ ). The power rule now handles
more than just powers of var.
>
differentiate( sin( x )^2, x );
2 sin(x) cos(x)
However, adding new functions to the differentiator is tedious and
error prone, and the job of handling the chain rule must be repeated for
each function recognized by it.
Introducing a Function TableMany functions (that you need to add)
and the rules used for their differentiation can be stored in a table as
follows:
a, b := op( expr );
if a = var and not has( b, var ) then
b * a ^ ( b - 1 ) * procname( a, var )
else
’procname( args )’
end if
elif type( expr, ’‘*‘’ ) then
a, b := op( 1, expr ), subsop( 1 = 1, expr );
procname( a, var ) * b + a * procname( b, var )
elif type( expr, ’function’ ) and nops( expr ) = 1 then
# functions of a single variable; chain rule
b := op( 0, expr ); # the name of the function
a := op( 1, expr ); # the argument
if assigned( functab[ b ] ) then
# This is a ‘‘known’’ function
functab[ b ]( a ) * procname( a, var )
else
# This function is not known; return unevaluated
’procname( args )’
66 •Chapter 2: Programming with Modules
>
>
>
>
>
end proc:
end if
else
’procname( args )’
end if
This not only simplifies the code used for the function case, but also
makes it very easy to add new functions.
DrawbacksUnfortunately, this implementation has serious drawbacks.
• It is not extensible. The known functions are hardcoded as part of
the procedure definition for differentiate. New functions cannot
be added without editing this source code.
• A second problem relates to performance. A complete implementation
would require a table of dozens or hundreds of functions. That large table would need to be created and initialized each time differentiate
is invoked.
Encapsulation and ExtensibilityOne way to fix both problems is to
make the table of functions a global variable. However, using global variables can be dangerous, because they pollute the user namespace and are
subject to unwanted inspection and tampering.
SolutionA better solution is to put the differentiate procedure,
along with its table of functions, into a module. The table is then initialized only once–when the module is created–and can be saved to a
Maple repository with the rest of the module by using a savelib call. By
making the table a local variable of the module, you prevent users from
modifying the table or otherwise inspecting it in unwanted ways.
This does not prevent you from making the differentiator user-
extensible, however. You can add an access procedure addFunc that allows
users to add rules for differentiating new functions. For example, you can
use the call
>
addFunc( ’cos’, x -> -sin(x) );
to add the derivative of the sin function. The export addFunc of the
DiffImpl module is a procedure that requires two arguments. The first
is the name of a function whose derivative is to be made known to the
differentiator. The second is a Maple procedure of one argument that
expresses the derivative of the function being added.
2.1 Syntax and Semantics •67
With this strategy in mind, you can create a module DiffImpl, with
principal export differentiate. At the same time, you can also make
the basic differentiation rules extensible.
Here is the complete source code for the differentiator with these im-
local b, e;
Assert( type( expr, ’‘^‘’ ) );
b, e := op( expr );
if has( e, var ) then
expr * ( differentiate( e, var ) * ln( b )
+ e * differentiate( b, var ) / b )
else # simpler formula
e * b^(e - 1) * differentiate( b, var )
end if;
To give the set of rules for nonfunctional expressions similar extensibility,
you can store those rules in a table. The table is indexed by the primary (or
basic) type name for the expression type, as given by the Maple procedure
whattype.
>
whattype( a + 2 );
>
whattype( a / b );
+
∗
2.1 Syntax and Semantics •69
>
whattype( a^sqrt(2) );
^
>
whattype( [ f( x ), g( x ) ] );
list
A rule is expressed by a procedure of two arguments, expr and var, in
which expr is the expression to be differentiated, and var is the variable
of differentiation. For instance, to make the differentiator handle items
such as sets and lists by differentiating their individual components, add
the rule
in the module body. The advantage of using this scheme is that, not
only can the author of the differentiator extend the system, but so can
users of the system. Having instantiated the module DiffImpl, any user
can add rules or new functions, simply by issuing appropriate calls to
addRule and addFunc.
The differentiator cannot handle the procedure tan.
>
differentiate( tan( x )/exp( x ), x );
tan(x)
−
x
e
differentiate(tan(x), x)
+
x
e
You must add it to the database of known functions.
>
DiffImpl:-addFunc( ’tan’, x -> 1 + tan(x)^2 );
x → 1 + tan(x)
>
differentiate( tan( x )/exp( x ), x );
2
70 •Chapter 2: Programming with Modules
tan(x)
−
1 + tan(x)
+
x
e
2
x
e
Similarly, there is not yet any rule for handling equations and other
relations.
>
differentiate( y( x ) = sin( x^2 ) - cos( x^3 ), x );
differentiate(y(x) = sin(x2) − cos(x3), x)
>
DiffImpl:-addRule( ’{ ‘=‘, ‘<‘, ‘<=‘ }’,
>
>
differentiate( y( x ) = sin( x^2 ) - cos( x^3 ), x );
differentiate(y(x), x) = 2 cos(x2) x + 3 sin(x3) x
() -> map( differentiate, args ) );
{() → map(
differentiate, args)}
2
The Extension Mechanism is Module AwareDo not confuse the extension mechanism previously proposed for the differentiator with the
extension mechanism used by the built-in Maple command diff. The
diff command uses a traditional string concatenation mechanism for
adding knowledge of the derivatives of functions, and all its rules are
built-in, so they cannot be extended. For instance, to add a new function F to the Maple built-in diff command, you can define a procedure
‘diff/F‘ that computes the derivative of F.
By contrast, the extension mechanism used in the differentiate
example is
module aware. To add knowledge of the derivative of some
top-level function F, you can issue a command, such as
>
DiffImpl:-addFunc( ’F’, x -> sin( x ) + cos( x ) );
x → sin(x) + cos(x)
The derivative of F( x ) is sin( x ) + cos( x ).) Define a module
with some special functions, one of which is also called F.
2.1 Syntax and Semantics •71
>
SpecFuncs := module()
>
>
>
export F; # etc.
# definition of F() and others
end module:
You can now add this new F to the known functions.
>
DiffImpl:-addFunc( SpecFuncs:-F, x -> exp( 2 * x ) );
(2 x)
x → e
>
differentiate( F( x ), x );
sin(x) + cos(x)
>
use SpecFuncs in
>
>
differentiate( F( x ), x );
end use;
e
(2 x)
With the traditional mechanism, this does not work.
>
‘diff/‘ || F := x -> sin( x ) + cos( x );
diff /F := x → sin(x) + cos(x)
>
diff( F( x ), x );
sin(x) + cos(x)
>
use SpecFuncs in
>
>
>
‘diff/‘ || F := x -> exp( 2 * x );
diff( F( x ), x );
end use;
diff /F := x → e
(2 x)
e
(2 x)
The definition for the global F has been lost.
>
diff( F( 2 * x ), x );
(4 x)
e
72 •Chapter 2: Programming with Modules
(You can use a different argument to diff to avoid recalling the an-
swer from its remember table.) The traditional mechanism fails because
it relies on the
external representation of names, and not upon their
bindings, so each attempt to define an extension to diff in fact adds a
definition for the derivative of
all functions whose names are spelled "F".
Note:A commented version of the differentiate module is available
in the samples/AdvPG directory of the Maple installation. The implementation shown in the text has been somewhat simplified.
2.2Records
The simplest way to use modules is as Pascal-style records (or structures,
as in C and C++). A record is a data structure that has some number
of named
values. Although the underlying data structure of a Maple record is currently a module, records and modules represent distinct abstractions. A
record is simply an aggregate data structure in which the members have
fixed names. Modules provide additional facilities such as computation at
initialization and access control.
slots or fields. In Maple, these slots can be assigned arbitrary
Instantiating RecordsTo create a record, use the Record constructor.
In the simplest form, it takes the slot names as arguments.
>
rec := Record( ’a’, ’b’, ’c’ );
rec :=
module() export a, b, c; option
record ; end module
The name rec is now assigned a record with slots named a, b, and c.
These are the slot names for the record rec. You can access and assign
these slots by using the expressions rec:-a, rec:-b, and rec:-c.
>
rec:-a := 2;
a := 2
>
rec:-a;
2
2.2 Records •73
If not assigned, the record slot evaluates to the
local instance of the
slot name.
>
rec:-b;
b
>
evalb( % = b );
false
This is useful because the entire record can be passed as an aggregate
data structure.
The record constructor accepts initializers for record slots. That is,
you can specify an initial value for any slot in a new or in an unassigned
record by passing an equation with the slot name on the left side and the
initial value on the right.
>
r := Record( ’a’ = 2, ’b’ = sqrt( 3 ) );
r := module() export a, b; option
>
r:-b;
record ; end module
√
3
In addition, you can attach type assertions to record slots. To introduce a type assertion, use a ‘::‘ structure with the slot name specified
as the first operand. Type assertions can be used in combination with
initializers. An incompatible initializer value triggers an assertion failure
when the assertlevel kernel option is set to 2. For more information,
refer to ?kernelopts.
>
kernelopts( ’assertlevel’ = 2 ):
>
Record( a::integer = 2.3, b = 2 );
Error, (in assign/internal) assertion failed in
assignment, expected integer, got 2.3
>
r := Record( ’a’::integer = 2, ’b’::numeric );
74 •Chapter 2: Programming with Modules
r := module()
export a::
option
integer, b::numeric;
record ;
end module
>
r:-b := "a string";
Error, assertion failed in assignment, expected
numeric, got a string
If the initializer for a record slot is a procedure, you can use the
reserved name self to refer to the record you are constructing. This
allows records to be self-referential. For example, you can write a complex
number constructor as follows.
Combined with prototype-based inheritance, described on page 76,
this facility makes the Record constructor a powerful tool for objectoriented programming.
Record TypesExpressions created with the Record constructor are of
type record.
>
type( rec, ’record’ );
true
This is a structured type that works the same way as the ‘module‘
type, but recognizes records specifically.
>
r := Record( a = 2, b = "foo" ):
>
type( r, ’record( a::integer, b::string )’ );
true
2.2 Records •75
Note:In a record type, the slot types are used to test against the values
assigned to the slots (if any), and are not related to type assertions on
the slot names (if any).
>
r := Record( a::integer = 2, b::{symbol,string} = "foo" ):
>
type( r, ’record( a::numeric, b::string )’ );
true
Using Records to Represent QuaternionsRecords are useful for implementing simple aggregate data structures for which named access to
slots is wanted. For example, four real numbers can be combined to form
a quaternion, and you can represent this using a record structure, as follows.
>
MakeQuaternion := proc( a, b, c, d )
>
>
>
Record( ’re’ = a, ’i’ = b, ’j’ = c, ’k’ = d )
end proc:
z := MakeQuaternion( 2, 3, 2, sqrt( 5 ) );
z := module()
export
option
re, i, j, k;
record ;
end module
In this example, z represents the quaternion 2 + 3i +2j +√5k (where
i, j , and k are the nonreal quaternion basis units). The quaternion records
can now be manipulated as single quantities. The following procedure accepts a quaternion record as its sole argument and computes the Euclidean
length of the quaternion that the record represents.
>
qnorm := proc( q )
>
>
>
>
>
use re = q:-re, i = q:-i, j = q:-j, k = q:-k in
sqrt( re * re + i * i + j * j + k * k )
end use
end proc:
qnorm( z );
√
22
A Maple type for quaternions can be introduced as a structured record
type.
76 •Chapter 2: Programming with Modules
>
TypeTools:-AddType( ’quaternion’, ’record( re, i, j, k )’ );
>
type( z, ’quaternion’ );
true
Object InheritanceThe Record constructor supports a simple form of
prototype-based inheritance. An object system based on prototypes does
not involve classes; instead, it uses a simpler, more direct form of objectbased inheritance. New objects are created from existing objects (called
prototypes) by cloning, that is, copying and augmenting the data and
behavior of the prototype.
The Record constructor supports prototype-based inheritance by accepting an index argument, which is the prototype for the new object
record.
>
p := Record( a = 2, b = 3 ); # create a prototype
p := module() export a, b; option
>
p:-a, p:-b;
record ; end module
2, 3
>
r := Record[p]( c = 4 );
r :=
module() export a, b, c; option
>
r:-a, r:-b, r:-c;
record ; end module
2, 3, 4
In this example, the record p is the prototype, and the second record
r inherits the slots a and b, and their values, from the prototype p. It also
augments the slots obtained from p with a new slot c. The prototype p is
not changed.
>
r:-a := 9;
a := 9
2.2 Records •77
>
p:-a;
2
Behavior, as well as data, can be copied from a prototype. To copy behavior, use a constructor procedure for both the prototype and its clones.
>
BaseComplex := proc( r, i )
>
Record( ’re’ = r, ’im’ = i )
>
end proc:
>
NewComplex := proc( r, i )
>
Record[BaseComplex(r,i)]( ’abs’ =
>
>
>
>
(() -> sqrt( self:-re^2 + self:-im^2 )) )
end proc:
c := NewComplex( 2, 3 ):
c:-re, c:-im, c:-abs();
√
2, 3,
13
An object created from a prototype can serve as a prototype for an-
subtype( ’record( re, im, abs )’, ’record( re, im )’ );
true
For example, NewComplex creates objects of a type that is a subtype
of the objects created by BaseComplex.
78 •Chapter 2: Programming with Modules
2.3Packages
Modules are ideal for writing Maple packages. They provide facilities for
large software projects that are better than table and procedure based
methods.
What Is a Package
package is a collection of procedures and other data, that can be treated
A
as a whole. Packages typically gather a number of procedures that enable
you to perform computations in some well-defined problem domain. Packages may contain data other than procedures, and may even contain other
packages (subpackages).
Packages in the Standard LibraryA number of packages are shipped
with the standard Maple library. For example, the group, numtheory,
codegen, and LinearAlgebra packages are all provided with Maple,
along with several dozen others. The group package provides procedures
that allow you to compute with groups that have a finite representation
in terms of permutations, or of generators and defining relations. The
LinearAlgebra package has a large number of procedures available for
computational linear algebra.
Table-Based PackagesMany packages are implemented as tables. The
essential idea underlying this implementation scheme is that the name of
a package routine is used as the index into a table of procedures. The
table itself is the concrete representation of the package.
Use Modules for New PackagesModules are the new implementation vehicle for packages. A module represents a package by its exported
names. The exported names can be assigned arbitrary Maple expressions,
typically procedures, and these names form the package.
Package ExportsSome of the data in a package is normally made accessible to the user as an export of the package. For packages implemented
as modules, the package exports are the same as the exports of the underlying module. For packages implemented as tables, the package exports
are the names used to index the underlying table.
Accessing the exports of a package is a fundamental operation that is
supported by all packages. If P is a Maple package, and e is one among its
exports, you can access e by using the fully qualified reference P[ e ]. If
P is a module, then you can also use the syntax P:-e. These methods of
accessing the exports of a module are normally used when programming
with a package.
2.3 Packages • 79
Note that :- is a left-associative operator. If S is a submodule of a
module P, and the name e is exported by S, then the notation P:-S:-e is
parsed as (P:-S):-e, and so it refers to the instance of e local to S. This
fact is important to reference members of subpackages. For example,
>
m := Matrix(2,2,[[1-x,2-x],[3-x,4-x]],
>
>
LinearAlgebra:-LA_Main:-Norm( m, 1, conjugate = false );
’datatype’ = ’polynom(integer)’ );
m :=
´
1 − x 2 − x
3 − x 4 − x
µ
max(|x − 1| + |x − 3| , |x − 2| + |x − 4|)
calls the procedure Norm in the subpackage LA_Main of the LinearAlgebra
package. You can use indexed notation for this.
Using Packages InteractivelyFor interactive use, it is inconvenient to
enter fully-qualified references to all the exports of a package. To ease
this burden, the Maple procedure with is provided for the interactive
management of package namespaces. Using with, you can globally impose
the exported names of a package. This allows you to access the package
exports, without typing the package prefix, by making the names of the
exports visible at the top-level of the Maple session. For example, to use
the numtheory package, you can issue the command
>
with( numtheory );
Warning, the protected name order has been redefined
and unprotected
The effect of this command is to make the names exported by the
numtheory package (a list of which is returned by the call to with) avail-
able temporarily as top-level Maple commands.
This section describes how to write Maple packages by using modules.
The following subsections present several examples that illustrate how to
do this.
The LinkedList Package
The first example package is a small package called LinkedList. This
example illustrates the basic structure of a package implemented by using
modules.
BackgroundLinked lists are a basic data structure used in programs
for many different purposes. There are many kinds of linked lists, with
variations on the basic idea intended to address performance and functionality issues. The example package shown in this subsection provides
a few operations on the simplest possible form of linked lists.
The links in a linked list are formed from a very simple data structured
called a
elements. Pairs can be modeled by fixed length records with two slots.
pair. A pair is essentially a container with space for exactly two
2.3 Packages • 81
When used to implement linked lists, the first slot holds the data for the
list entry, and the second slot stores a pointer to the next pair in the list.
The LinkedList package implements an abstract data definition for
the pair data structure, and adds some higher level operations on pairs
to effect the list abstraction. A linked list is effectively represented by its
first pair.
The pair abstract data structure is very simple. It consists of a constructor pair, and two accessors called
head and tail that satisfy the
algebraic specification
p = pair(head(p), tail(p))
for each pair p. In addition, there is a distinguished pair nil, satisfying
this algebraic relation, that is unequal to any other pair, and satisfies
head(nil) = nil, tail(nil) = nil.
Note that linked lists are quite different from the Maple built-in list
structures, which are really immutable arrays. Linked lists are best suited
for applications in which you want to incrementally build up the list from
its members.
1
Package ImplementationThe LinkedList package is implemented as
a module containing the primitive operations on pairs, and higher level
operations that implement the list abstraction.
>
macro( _PAIR = ‘‘ ): # for nice printing
>
LinkedList := module()
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
Lisp programmers will recognize the pair, head, and tail operations as the more
traditional operations known as “consÔ, “carÔ and “cdrÔ.
description "routines for simple linked lists";
export
Normally, a package definition like this would be entered into a Maple
source file using a text editor, or in a worksheet using the Maple graphical
user interface. In either case, the definition would then be followed by a
call to the savelib procedure using the name of the module as its sole
84 •Chapter 2: Programming with Modules
argument:
>
savelib( ’LinkedList’ );
Evaluating the savelib call saves the module to the first repository
found in the global variable libname, or the repository named with the
global variable savelibname, if it is defined. (At least one of these must
be defined.)
ImportantAlways ensure that the standard Maple library is writeprotected to avoid saving expressions in it. If you accidentally save something to the standard Maple library, you may need to restore the original
from the media on which you obtained the Maple software.
The package exports are listed as the exports of the module. A few
local variables are used to implement the package. The local procedures
map1 and reverse1 are part of the package implementation that is not
available to users of the package. They are visible only within the module
definition. This allows the package author to make improvements to the
package without disturbing any code that uses it. If the local procedures
reverse1 and map1 were exported (thus, available to users), it would be
difficult for the author to replace these routines without breaking existing
code that relies upon them.
The package includes two special (local) procedures, setup and
cleanup. These are executed, respectively, when the module is first read
from a repository, and when the package is either garbage collected or
when Maple is about to exit.
Using the PackageThe package exports can always be accessed by
using the long form of their names.
>
LinkedList:-pair( a, b );
(a, b)
For consistency with the older table-based package implementations,
an indexed notation can also be used.
>
LinkedList[ ’pair’ ]( a, b );
(a, b)
2.3 Packages • 85
This form requires that the index (in this case, the symbol pair) be
protected from evaluation, and the notation does not extend to packages
with nested subpackages.
To access the package exports interactively, use the with command.
>
with( LinkedList );
Warning, the protected names length, map and member
have been redefined and unprotected
Note that, since some of the package exports shadow global procedures
with the same name, the with command issues warnings. These warnings
are normal. They remind you that these names now refer to expressions
different from the expressions to which they referred previously. Once the
exports of the package LinkedList have been bound, you can call them
as you would global Maple routines with those names. Note that you can
still access the global version of member, for example, by using the syntax
:-member.
>
use LinkedList in
>
member( a, %% );
>
:-member( a, [ a, b, c, d ] )
>
end use;
true
true
This is one of the principal advantages of using modules and binding,
rather than assignment, to implement packages.
Lists are either built incrementally using the pair export of the package, or by calling the list export.
>
L := nil:
>
for i from 1 to 10 do
>
>
L := pair( i, L )
end do;
86 •Chapter 2: Programming with Modules
L := (1,
L := (2, (1,
L := (3, (2, (1,
L := (4, (3, (2, (1,
L := (5, (4, (3, (2, (1,
L := (6, (5, (4, (3, (2, (1,
nil )
nil ))
nil )))
nil ))))
nil )))))
nil ))))))
L := (7, (6, (5, (4, (3, (2, (1,
L := (8, (7, (6, (5, (4, (3, (2, (1,
L := (9, (8, (7, (6, (5, (4, (3, (2, (1,
L := (10, (9, (8, (7, (6, (5, (4, (3, (2, (1,
>
length( L );
10
>
member( 3, L );
nil )))))))
nil ))))))))
nil )))))))))
nil ))))))))))
true
>
member( 100, L );
false
>
reverse( L );
(1, (2, (3, (4, (5, (6, (7, (8, (9, (10,
>
map( x -> x^2, L );
(100, (81, (64, (49, (36, (25, (16, (9, (4, (1,
>
member( 100, % );
true
nil ))))))))))
nil ))))))))))
>
L2 := list( a, b, c, d );
L2 := (d, (c, (b, (a, nil ))))
>
map( sin, L2 );
2.3 Packages • 87
(sin(d), (sin(c), (sin(b), (sin(a),
>
eval( L2, { a = 1, b = 2, c = 3, d = 4 } );
nil ))))
nil ))))
>
map( evalf[ 10 ], % );
(4., (3., (2., (1.,
(4, (3, (2, (1,
nil ))))
Code Coverage Profiling Package
The following example is a package called coverage. It instruments procedures and modules for coverage profiling, that is, turns on statement-level
tracing. It serves as an example of a small package, and illustrates ways
in which modules can be manipulated.
DesignYou can write tests that exercise each part of the program to
ensure that the program:
• Works correctly
2
• Continues to work when it, or other programs on which it depends,
change over time.
It is important to be able to determine whether each statement in a
procedure is executed by some test case. The traceproc option of the
Maple command debugopts provides that capability. It takes the name p
of a procedure, using the syntax
2
The Maple CodeTools package provides tools for profiling code and testing code
coverage. For more information, refer to ?CodeTools.
88 •Chapter 2: Programming with Modules
debugopts( ’traceproc’ = p );
and instruments the procedure assigned to p for coverage profiling. Here
is an example.
>
p := proc( x )
>
>
>
>
>
>
>
if x < 0 then
2 * x
else
1 + 2 * x
end if
end proc:
debugopts( ’traceproc’ = p ):
Once the procedure has been instrumented, each time it is executed,
profiling information at the statement level is stored. To view the profiling
information, use the procedure showstat.
>
p( 2 );
5
>
showstat( p );
p := proc(x)
|Calls Seconds Words|
PROC |10.00012|
1 |10.00012| if x < 0 then
2 |00.0000|2*x
else
3 |10.0000|1+2*x
end if
end proc
The display shows that only one branch of the if statement that forms
the body of p was taken so far. This is because only a non-negative argument has been supplied as an argument to p. To get complete coverage,
a negative argument must also be supplied.
>
p( -1 );
−2
>
showstat( p );
2.3 Packages • 89
p := proc(x)
|Calls Seconds Words|
PROC |20.00024|
1 |20.00024| if x < 0 then
2 |10.0000|2*x
else
3 |10.0000|1+2*x
end if
end proc
The display shows that each statement in the body of p has been
reached.
To display the profiling information, use the debugopts command
with the traceproctable=procedure_name equation argument.
>
debugopts( traceproctable=p );
2 0 24
2 0 24
1 00
1 00
The package illustrated in this section helps extend this functionality
to modules, and acts as an interface to the debugopts with the traceproc
option.
The coverage package has two exports: profile and covered. Two
private procedures, rprofile and traced, are used as subroutines. They
are stored in local variables of the underlying module of the package.
The Package SourceHere is the source code for the package.
error "only procedures and modules can be profiled"
end if
end proc;
# Subroutine to recognize non-builtin procedures
userprocs := proc( s)
type( ’s’, procedure) and not( type( ’s’, builtin ) )
end proc;
# Subroutine to recognize profiled procedures
traced := proc( s )
debugopts( ’istraceproced’ = ’s’ )
end proc;
# Determine which procedures have
# coverage information.
covered := proc()
local S;
S := [ anames( ) ];
S := select( userprocs, S );
S := select( traced, S );
if nargs > 0 and args[ 1 ] = ’nonzero’ then
S := select( s -> evalb( s[1,1] <> 0 ), S )
elif nargs > 0 then
error "optional argument is the name nonzero"
end if;
map( parse, map( convert, S, ’string’ ) )
end proc;
end module:
How the Package WorksThe export profile is an interface to the
package’s principal facility: instrumenting procedures and modules for
coverage profiling. It returns the number of procedures instrumented, and
calls the private subroutine rprofile to do most of the work.
1. The procedure rprofile accepts a name s as an argument. If s is the
name of a procedure, rprofile simply calls debugopts to instrument
the procedure assigned to that name. Otherwise, if s is the name
of a module, rprofile selects any exports of the module that are
Loading...
+ hidden pages
You need points to download manuals.
1 point = 1 manual.
You can buy points or you can get point for every manual you upload.