Contents Previous Next Index
6 Procedures
A Maple procedure is a sequence of parameter declarations, variable declarations, and statements that encapsulates a computation. Once defined, a procedure can be used to perform the same computation repeatedly for different argument values, from different places in a program, or both. A procedure in Maple corresponds to a function in languages such as C or Java, a procedure or function in Pascal, or a subroutine in FORTRAN and modern versions of BASIC.
Chapter 1 gave a brief introduction to procedures. This chapter describes the syntax and semantics of procedures in detail, and discusses how to best make use of procedures in your programs.
6.1 Terminology
Several terms are used frequently when discussing procedures in Maple and other programming languages. Some of these terms are sometimes used interchangeably, but the distinctions between them are important:
Procedure - In Maple, a procedure is an object that can be invoked by a function call, be passed arguments, perform some operations, and return a result. A procedure definition begins with the keyword proc, and ends with end proc.
Function Call - A function call, of the form name(arguments), evaluates the arguments and then invokes a procedure if name has a value that is a procedure. The value of the function call is then the value returned by the procedure. If name has no value, then the value of the function call is just name(evaluatedArguments).
Argument - An argument is one of one or more values explicitly included in a function call. Note that a default value is not an argument.
Parameter or Formal Parameter - A parameter is a name that is declared in a procedure definition to receive the value of an argument. The parameter name is used to refer to that value within the body of the procedure.
Actual Parameter - An actual parameter is neither an argument nor a (formal) parameter. The term refers to the value that a formal parameter takes during the execution of a procedure. This value can come from an argument or a default value. The term is defined here for completeness; it is not further used in this chapter. Instead we will refer to the value of the parameter.
6.2 Defining and Executing Procedures
A Maple procedure definition has the following general syntax:
proc( parameterDeclarations ) :: returnType;
description shortDescription;
option optionSequence;
local localVariableDeclarations;
global globalVariableDeclarations;
statementSequence
end proc
A procedure definition is considered to be an expression in Maple, the evaluation of which produces the procedure itself. The resulting procedure is usually assigned to a name, but it can also be used in other ways such as passing it as an argument to another procedure, or invoking it immediately.
The following is a simple procedure definition. It contains two formal parameters, x and y, and one statement in the procedure body. There is no description, there are no options, and the procedure does not make use of any local or global variables. In order to be able to use the procedure later, we'll assign it to a name:
SumOfSquares := proc( x, y ) x^2 + y^2 end proc;
SumOfSquares≔procx,yx^2+y^2end proc
This procedure computes the sum of the squares of its two arguments. The procedure can be called with any two arguments and Maple will attempt to compute the sum of their squares. Like any computation in Maple, the result can be symbolic. If you want to restrict the types of arguments that are permitted, it is possible to specify the type for each argument in the parameter declarations, as described in the next section.
You can invoke (or execute) a procedure by using it in a function call:
procedureName( argumentSequence )
The procedureName is usually the name that the procedure was assigned to, although it can also be an actual procedure definition, or another expression that evaluates to a procedure.
The argumentSequence is a sequence of expressions that will be evaluated, and then substituted for the corresponding parameters before the execution of the statements comprising the body of the procedure. Note that the arguments are evaluated only once before the execution of the procedure begins. They are not evaluated again during execution of the procedure.
The value returned by the procedure is the result of the last statement executed within the procedure. In the following function call, Maple executes the statements in the body of the procedure SumOfSquares, replacing the formal parameters x and y with the arguments a and 3. The result of the last (and in this case, only) statement in the procedure is the returned value:
SumOfSquares(a,3);
a2+9
For more information about return values, see Returning Values from a Procedure.
6.3 Parameter Declarations
In the procedure definition, parameterDeclarations is a sequence of parameter declarations. Procedure parameter declarations can range from very simple to very sophisticated. In its simplest form, a parameter declaration is just the parameter's name. When you call the procedure, you can pass any value as an argument for such a parameter, and if you pass no value at all, the parameter will have no value.
You can extend a parameter declaration by adding a type specification and/or a default value. A type specification ensures that, when the procedure is called, the value of the parameter within the procedure will be of the indicated type, and a default value ensures that a parameter will always have a value even if no corresponding argument was passed.
Maple procedures can also have keyword parameters. When invoking a procedure, the corresponding arguments are of the form keyword=value, and can appear anywhere in the argument sequence.
When you call a procedure, the arguments are evaluated and then bound to the parameters. In the simplest case, there is a one-to-one correspondence between arguments and parameters; the first argument is bound to the first parameter, the second argument to the second parameter, and so on. The presence of default values and keyword parameters can change this correspondence, as described in this section.
Required Positional Parameters
A required positional parameter is called required because a corresponding argument must have been passed in the function call that invoked the procedure if the parameter is used during the execution of the procedure. It is called positional because the argument's position within argumentSequence must correspond to the position of the parameter in parameterDeclarations.
The syntax to declare a required positional parameter is:
parameterName :: parameterType
The parameterName must be a valid symbol, and is used to refer to the parameter within the procedure body. The :: parameterType is optional. If it is present and the corresponding argument does not match the specified type, an exception is raised.
In this example, the procedure Adder is defined with two parameters, a and b. The procedure returns the sum of its two arguments. For the parameter a, Adder expects an argument of type integer.
Adder := proc( a::integer, b ) a+b end proc:
Adder(2,3);
5
The next call to Adder raises an exception because the second argument is missing.
Adder(3);
Error, invalid input: Adder uses a 2nd argument, b, which is missing
This call raises an exception because the supplied first argument does not match the parameter's specified type.
Adder(2.5,4);
Error, invalid input: Adder expects its 1st argument, a, to be of type integer, but received 2.5
If a procedure has both required and ordered parameters (described below), all of the required parameters must appear before the ordered parameters.
Optional Ordered Parameters
An optional ordered parameter is declared in the same way as a required positional parameter, with the addition of a default value:
parameterName :: parameterType := defaultValue
The presence of defaultValue allows the parameter to be optional. If there are no remaining arguments or the next unused argument does not match the specified parameterType, the parameter receives the default value. The non-matching argument, if any, remains available for binding to a later parameter.
As was the case with a required positional parameter, :: parameterType can be omitted. The parameter will receive its default value only when there are no more available arguments, since any available argument would have been valid for an untyped parameter.
Usually, defaultValue will be of the type specified by parameterType, but this need not be the case. The default value can be a literal value of any other type, or NULL. If the default value is not a literal value, but is an expression that evaluates to something other than itself, then the result of that evaluation must conform to parameterType.
This class of parameters is called ordered because the arguments are bound to parameters in the order they were passed. If the first unused argument is not bound to the current parameter, it remains as the first available argument for the next parameter.
In this example, the procedure Adder is defined with two optional ordered parameters, a and b, both of type integer, and returns their sum:
Adder := proc(a::integer := 10, b::integer := 100.1) a + b end proc:
Adder(3,4);
7
103.1
Adder();
110.1
Adder(3,6.6);
In the first call to Adder, the arguments 3 and 4 were bound to the parameters a and b, and their sum returned. In the second call, only a single argument was passed, so b received its default value. Notice that the default value is not an integer, but since it is a literal value, is an acceptable default. In the third call, no arguments were passed and both parameters received their default values.
You may have expected the result of the fourth call to Adder to be 9.6, but this is not the case. Why? First, parameter a was given the value 3. Next, 6.6 was considered a candidate for parameter b, but was rejected because it is not of type integer. Instead, b received its default value.
This illustrates an important aspect of calling procedures in Maple, which is that in general, it is acceptable to call a procedure with more arguments than it expects. You will see later how to access these within a procedure, allowing you to write procedures that accept a variable number of arguments, or how to disallow the passing of extra arguments.
Expected Ordered Parameters
An expected ordered parameter is similar to an optional ordered parameter, except that the corresponding argument can be omitted only if all further arguments are also omitted. If there is an argument available, it must match parameterType or an exception is raised.
The declaration of an expected ordered parameter declaration differs from that of an optional ordered parameter by enclosing parameterType in expects():
parameterName :: expects( parameterType ) := defaultValue
The procedure below is identical to the one from the previous section, except that parameter b has been declared as an expected parameter. When it is called with a second argument of the wrong type, instead of saving that argument for a later parameter, Maple raises an exception:
Adder := proc(a::integer := 10, b::expects(integer) := 100.1) a + b end proc:
Error, invalid input: Adder expects its 2nd argument, b, to be of type integer, but received 6.6
Keyword Parameters
Keyword parameters are not positional and not ordered. A keyword parameter is bound to a value when an argument of the form keyword=value appears in a procedure invocation. The left-hand side of such an argument specifies the keyword parameter name, and the right-hand side specifies the value it will receive. If true is an acceptable value for the parameter, then an argument of the form keyword is equivalent to keyword=true.
The declaration of a keyword parameter looks very much like that of an optional ordered parameter, except that all keyword parameter declarations are enclosed in braces, much like a set is:
{ ... parameterName :: parameterType := defaultValue ... }
The :: parameterType can be omitted, in which case any value can be passed as the right-hand side of the keyword argument. If parameterType is specified, then the passed value must be of that type.
As is the case with an ordered parameter, if defaultValue is a literal value, it need not match parameterType.
A procedure can have multiple keyword parameters, which can be declared within a single set of braces, or grouped into multiple sets of braces as desired to improve source code readability. When a procedure is compiled into Maple's internal form, the keyword parameters are consolidated into a single set. If you then display that procedure using Maple's print command, the keyword parameters are displayed as a single set, sorted lexicographically.
The simplest and most frequently encountered form of keyword parameter declaration has a single Maple symbol for parameterName:
Simple := proc( { simple::integer := 2 } ) sprintf("simple=%d",simple) end proc:
Simple(simple=3);
simple=3
Simple();
simple=2
Simple(simple=4.5);
Error, invalid input: Simple expects value for keyword parameter simple to be of type integer, but received 4.5
It is also possible to declare keyword parameters that can be referred to by indexed names when the procedure is called. If parameterName is of the form `symbol[symbol]` or `symbol[integer]`, it matches indexed names.
The indexed parameter names are still symbols because of the enclosing left single quotes, and are referenced that way within the procedure, but the argument names can be actual indexed names. For more information on indexed keyword arguments, see Binding of Arguments to Parameters.
As a convenience to the user of a procedure, multiple spellings of the keyword are allowed by specifying a list of the permitted spellings in the declaration:
{ ... [ parameterName1, parameterName2, ... ] :: parameterType := defaultValue ... }
Within the procedure's statementSequence, you can refer to the parameter by any of the declared spellings. If you display the procedure using print, however, only the first spelling is used.
Spellings := proc( { [color,colour]::symbol := RED } ) sprintf("color=%a -- colour=%a", color, colour) end proc;
Spellings≔proccolor,colour::symbol ≔ REDsprintf⁡color=%a -- colour=%a,color,colorend proc
Spellings();
color=RED -- colour=RED
Spellings(color=BLUE);
color=BLUE -- colour=BLUE
Spellings(colour=GREEN);
color=GREEN -- colour=GREEN
Spellings(color=ORANGE,colour=PURPLE);
color=PURPLE -- colour=PURPLE
Spellings(colour=YELLOW,color=42);
Error, invalid input: Spellings expects value for keyword parameter [color, colour] to be of type symbol, but received 42
If more than one keyword argument matches a keyword parameter, only the last one takes effect.
Alternate spellings and indexed keywords can be combined by including the indexed keyword symbols in the list of alternate spellings.
The End-of-Parameters Marker
Recall from earlier that Maple usually allows extra arguments to be passed to a procedure. This is useful when implementing procedures that can accept a variable number or type of arguments, but for many procedures, the presence of extra arguments indicates a programming error.
A procedure can be declared to disallow extra arguments (that is, arguments that were not bound to any declared parameter) by ending the sequence parameterDeclarations with $. If extra arguments remain at the end of argument processing, Maple raises an exception:
TwoSine := proc( x::float := 0.0, $ ) 2 * sin(x) end proc:
TwoSine(2.3);
1.491410424
TwoSine();
0.
TwoSine(2.3,-4.5);
Error, invalid input: too many and/or wrong type of arguments passed to TwoSine; first unused argument is -4.5
TwoSine(42);
Error, invalid input: too many and/or wrong type of arguments passed to TwoSine; first unused argument is 42
Default Value Dependencies
You can express the default value defaultValue of a parameter in terms of other parameters, as long as the resulting value conforms to the specified type parameterType, if any. The parameters on which defaultValue depends can appear earlier or later in parameterDeclarations. For example, here is a list extraction function that expects a list, a starting index, and an ending index. If the ending index is omitted, the length of the list is used:
SubList := proc( s::list, f::integer := 1, t::integer := numelems(s) ) s[f..t] end proc:
SubList([a,b,c,d,e],2,3);
b,c
SubList([a,b,c,d,e],2);
b,c,d,e
There can be no cyclic dependencies, such as two parameters' default values depending on each other:
NotGood := proc( s := sin(c), c := cos(s) ) s^2 + c^2 end proc;
Error, (in NotGood) cyclic dependency detected in parameter s := sin(c)
Usually, Maple evaluates the arguments of a function call from left to right. The use of parameter dependencies in default values will alter this order to ensure that the required values are available by the time they are needed. This is only of consequence if the evaluation of one or more arguments has side effects.
Parameter Modifiers
Parameter modifiers change the way that arguments are evaluated and/or bound to parameters. Modifiers appear as part of the parameter's declaration, in the form of a function call enclosing the parameter type parameterType.
You have already seen the expects modifier, which changes an optional ordered parameter into an expected ordered parameter.
The seq Modifier
The seq modifier allows the parameter to match multiple arguments. When a parameter with a specified type of the form seq(memberType) is encountered, it is bound to an expression sequence of all arguments (beginning with the next available one) that are of the type specified by memberType.
parameterName :: seq(memberType)
If no arguments match memberType, the parameter will receive its default value if one was specified, or NULL if there is no default value.
The seq modifier cannot be used together with the expects modifier, because seq is allowed to match zero arguments, whereas expects implies that at least one argument must match. The seq modifier also cannot be used with a keyword parameter.
You must be careful when working with the value of a seq parameter because it might have just one element in it. Such a value is not considered to be a sequence, thus indexing it will not select the element. The safest approach is to enclose the parameter in a list, as in this example:
LargestInteger := proc( x::seq(integer), other::seq(anything) ) local max, n; max := -infinity; for n in [x] do if n > max then max := n end if end do; max, [other] end proc:
LargestInteger(4,7,8,2,1);
8,
LargestInteger(4,7,"not an integer",8,2,1);
7,not an integer,8,2,1
The type seq(uneval) matches all remaining arguments, and returns them as a sequence thereof, remaining unevaluated. For more information, see The uneval Modifier below.
The depends Modifier
Usually, a parameter's type is predetermined when the procedure is first written. When arguments are matched to parameters, parameterType is not evaluated since this is not expected to yield anything other than what was written. There are cases where this is too restrictive. In that case, use the depends modifier to declare that a parameter's type depends on something that could change. Such a dependency is usually on another parameter.
The syntax for a parameter declaration with the depends modifier is:
parameterName :: depends( parameterTypeExpression )
where parameterTypeExpression is a type expression that can refer to other parameter names.
For example, you might want to write a procedure like this to find one root of a polynomial:
OneRoot := proc( p::depends(polynom(integer,v)), v::symbol ) local sols; sols := [ solve(p=0,v) ]; if sols = [] then error "no solution" else sols[1] end if end proc:
OneRoot(x^2+3*x+5,x);
−32+I⁢112
OneRoot(x^2+3*x+5,y);
Error, invalid input: OneRoot expects its 1st argument, p, to be of type polynom(integer,y), but received x^2+3*x+5
This procedure expects as an argument for its first parameter, p, a polynomial in the variable specified by the second parameter, v. If the depends modifier were omitted, the procedure would only accept polynomials in the global variable v.
The depends modifier can only be used for required parameters. It cannot be used for optional or expected ordered parameters, nor keyword parameters. If the depends modifier is used together with the seq modifier, it must appear within it. That is, parameterType must be written in the form seq(depends(memberType)).
The uneval Modifier
Unlike the other modifiers described so far, the uneval modifier takes no arguments. That is, it does not enclose another type or modified type. Instead it is used as the parameterType.
parameterName :: uneval
A parameter with the uneval modifier prevents the corresponding argument from being evaluated when the procedure is called. The effect is the same as if the argument had been enclosed in unevaluation quotes ('...').
The uneval modifier can only be used for required positional parameters, and cannot be used in conjunction with any other modifiers. It also cannot be used for any parameter declaration after one that uses the seq modifier.
Square := proc( x::uneval ) x^2 end proc:
(a, b) := (3, 4.5):
r := Square(a+b);
r≔a+b2
eval(r);
56.25
The combined type specification, seq(uneval), matches all remaining arguments and returns a sequence of all of them, unevaluated. Note that this declaration must appear as the last specification of the procedure parameter.
The evaln Modifier
A parameter declared with the evaln modifier expects an argument that can be evaluated to a name (that is, an assignable object). This modifier can be used in two different forms, evaln or evaln(valueType). In the second form, the resulting name is expected to have a value that matches the type valueType.
parameterName :: evaln
parameterName :: evaln(valueType)
In effect, declaring a parameter with the evaln modifier is equivalent to enclosing the argument with evaln at procedure invocation time, and allows you to write procedures where the user of the procedure does not have to remember to do so.
Like uneval, the evaln modifier can only be used for required positional parameters, and cannot be used for a parameter declaration after one having a seq modifier. The only other modifier that can be used together with evaln is the depends modifier, in the form depends(evaln(valueType)).
SquareName := proc( x::evaln(integer) ) x^2 end proc:
In the first call, the argument a is evaluated to 'a', which is a name with an integer value.
SquareName(a);
a2
In the next call, the argument b is evaluated to 'b', which is a name, but not with an integer value.
SquareName(b);
Error, invalid input: SquareName expects its 1st argument, x, to be of type evaln(integer), but received b := 4.5
In the next call, the argument does not evaluate to a name.
SquareName(a+b);
Error, illegal use of an object as a name
In the next example, the procedure Accumulate accumulates all the values of its second argument in the variable passed as its first argument. Notice that the first call fails, because Accumulate expects a name with a numeric value, but total has not been initialized yet.
Accumulate := proc( r::evaln(numeric), n::numeric ) r := eval(r) + n end proc:
Accumulate(total,2);
Error, invalid input: Accumulate expects its 1st argument, r, to be of type evaln(numeric), but received total := total
total := 0;
total≔0
2
Accumulate(total,3.5);
5.5
total;
The coercion Modifiers
parameterName :: (valueType)
parameterName :: coerce(valueType,coercion procedure)
As stated previously in Procedures, coercion refers to the ability to pass one type of data to a procedure and have it receive a different type.
Coercion can be enabled in two ways:
Coercion Using ~Type: You can use a short form notation to invoke Maple built-in coercion functions. This short form notation is a tilde (~) followed by a data type. For example, the command ~Matrix will accept, among other things, a listlist and return a Matrix. This type of ~ function can be used in place of the data type in a procedure declaration. This tells Maple to try testing if the passed parameter is of that type, and if not, call the ~ function to coerce it into that type.
Coercion Using coerce(): You can use long form notation to enable data coercion by using the coerce() modifier. The coerce modifier allows you to specify a sequence of types and coercion procedures. A coercion procedure is a procedure that accepts a single typed parameter and converts that parameter into a new expression. When the main procedure is called, the argument is type checked against the parameter types handled by the coercion procedure. The first coercion procedure whose parameter's type matches the type of the argument is called. The return value of the matching coercion procedure is then used as the parameter's value.
p_string :=proc(s::coerce(string, (s::name)->convert(s,string))) s; end proc;
p_string≔procs::coerce⁡string,s::name→convert⁡s,stringsend proc
p_string("a string");
a string
p_string(`a name`);
a name
Procedures without Declared Parameters
You can define a procedure without any declared parameters. Some procedures, such as one that generates random numbers, might not depend on any arguments. Other procedures might operate directly on global values, although this is considered poor programming practice.
However, just because a procedure has no declared parameters does not mean that it cannot be passed arguments. Unless a procedure's parameterDeclarations ends with $, it is always permissible to pass more arguments than there are declared parameters. All of the arguments are accessible via the special sequence _passed, which has one entry corresponding to each argument that was passed. The number of entries is given by _npassed. For example, the following procedure produces the sum of all its arguments:
SumOfArgs := proc( ) add(_passed[i], i=1.._npassed) end proc:
Warning, (in SumOfArgs) `i` is implicitly declared local
SumOfArgs(42,3.14,sin(-2.5));
44.54152786
For more information on _passed and _npassed as well as other special names for working with parameters, see Special Sequences for Referring to Parameters and Arguments.
6.4 Return Type
The closing parenthesis following a procedure's parameter declarations can be followed by :: and a returnType assertion. This is optional. Unlike a parameterType specification, returnType is only an assertion. If kernelopts(assertlevel) is set to 2, the type of the value returned by the procedure is checked against the type specified by returnType, and if it does not match, an exception is raised:
ReturnInteger := proc( x ) :: integer; x^2 end proc:
kernelopts(assertlevel=2):
ReturnInteger(3);
9
ReturnInteger(Pi);
Assertions are useful for identifying programming errors. For more information, see Using Assertions.
6.5 The Procedure Body
The body of the procedure is where most of the computation is carried out (although some computation may already have occurred while resolving the defaultValue for optional parameters). The procedure body consists of an optional description, option declarations, local and global variable declarations, and executable statements.
The description, option, local variable, and global variable declaration parts are each introduced by their own keyword, and can appear in any order. There can be only one description clause and one option clause. There can be any number of variable declaration clauses.
Description
Use the description clause to give a procedure a short description that is displayed when the procedure is displayed. The description has no effect on the execution of the procedure. It is only used for documentation purposes.
description string, string, ... ;
The description keyword is followed by one or more string literals, separated by commas.
Average := proc( x::integer, y::integer ) description "Compute the average of two integers.", "Returns a rational."; (x + y) / 2; end proc;
Average≔procx::integer,y::integerdescriptionCompute the average of two integers.,Returns a rational.;1/2*x+1/2*yend proc
Options
A procedure can be tagged with one or more options which alter the behavior or display of the procedure. Options are specified by the keyword option or options, followed by one or more option names or equations, separated by commas:
option optionNameOrEquation, ... ;
options optionNameOrEquation, ... ;
Each optionNameOrEquation is a symbol or an equation of the form optionName=value. Any symbol is allowed as an option name that you can use to tag procedures for your own purposes, but there are several options that are known to Maple.
The arrow and operator Options
The arrow option and the operator option have meaning when specified together. These options cause Maple to print the procedure using arrow notation:
SumOfSquares := proc( x, y ) option operator, arrow; x^2 + y^2; end proc;
SumOfSquares≔x,y↦x2+y2
For information on defining a procedure using arrow notation, see Functional Operators: Mapping Notation
The builtin Option
Maple has two classes of procedures: kernel built-in procedures implemented in the C programming language, and library procedures written in the Maple programming language. Because the kernel built-in functions are compiled, you cannot view their procedure definitions. The builtin option identifies a kernel procedure.
This option is shown when you display a purely built-in procedure. Instead of displaying the procedure statements, only the builtin option is displayed.
For example, the add procedure is built into the kernel:
print(add);
procoptionbuiltin=add;end proc
A procedure can have both the builtin option and a statement sequence. In that case, invoking the procedure will first invoke the kernel built-in version. If that indicated that it did not compute a result, the statement sequence is executed instead. This mechanism allows the kernel to process common cases very quickly, and defer to library code to handle other cases.
You can use the type function to test if an expression is a built-in procedure. An expression is of type builtin if it is a procedure with option builtin:
type(add, 'builtin');
true
type(int, 'builtin');
false
You cannot create built-in procedures, although there is a mechanism for creating procedures based on externally compiled code. Such procedures have the call_external option.
The call_external Option
The call_external option appears in procedures generated by the define_external procedure. This option indicates that the implementation of the procedure resides in a pre-compiled external library. For more information, see External Calling: Using Compiled Code in Maple.
The hfloat Option
The hfloat option forces all floating-point operations within a procedure to be performed using hardware floating-point values. Depending on the operations performed, this can significantly speed up execution of the procedure at the cost of floating-point accuracy. Procedures that perform many floating-point operations or manipulate the contents of Arrays, Matrices, or Vectors of hardware floating-point values will benefit the most from this option.
The hfloat option causes the following differences in the procedure's definition and execution:
Any floating-point constants appearing in the procedure body are converted into hardware floating-point values when the procedure is first created.
Numeric arguments passed to the procedure are converted into hardware floating-point values when the procedure is invoked.
Extracting values from hardware floating-point Arrays, Matrices, and Vectors does not incur a conversion to arbitrary precision floating-point form. Instead, the hardware floating-point values are used directly.
Calls to evalhf made from within the procedure return a hardware floating-point value, and thus do not incur a conversion to arbitrary precision floating-point form.
These differences, together with the rules for contagion of hardware floating-point values in expressions, will usually cause arithmetic operations in the procedure to be performed using hardware floating-point arithmetic.
The use of the hfloat option differs from using evalhf in a few ways:
When a procedure is executed within the evalhf environment, everything is computed using hardware floats, and the operations available are restricted to those that can be done using hardware floats. No other basic data types, such as integers or strings, are available.
The only data structures available within the evalhf environment are Arrays.
Performance of a procedure having option hfloat is generally better than one operating with arbitrary precision floats, but usually not as good as a procedure operating within evalhf. But, a procedure with option hfloat has the full power of Maple available to it. All Maple operations, data types (except arbitrary precision software floating point), and data structures can be used in such a procedure.
The hfloat option cannot be used in conjunction with the builtin, call_external, or inline options.
Hardware floating-point numbers and computations are discussed in detail in Numerical Programming in Maple. For more information on hardware floating-point contagion, see Floating-Point Contagion.
The inline Option
Use the inline option to create a procedure that can be expanded inline wherever it is called from. An inline procedure avoids the overhead of a procedure invocation by executing the procedure's statements directly as if it were written in-line instead of in a separate procedure. This can result in improved execution speed and reduced memory usage.
Not all Maple procedures can take advantage of the inline option. Only procedures whose body consists of a single expression or an expression sequence can be expanded in-line. The body cannot consist of a statement or statement sequence. For details on further restrictions that may apply, refer to the inline help page.
The overload Option
The presence of option overload in a procedure indicates that the procedure will operate only on arguments matching the declared parameters (as is normally the case), and that if the arguments do not match the parameters, the next in a sequence of such procedures is tried.
A sequence of procedures with option overload can be combined into a single procedure using the overload command. This will produce a new procedure that will, when called, try each overload procedure in turn until one is encountered that will accept the arguments, or no procedures remain. In the latter case, an exception will be raised.
The following example uses the overload command and procedures with the overload option to append an entry to either a list (by creating a new list) or a 1-dimensional Array (in-place):
Append := overload( [ proc( L::list, x::anything ) option overload; [ op(L), x ] end proc, proc( A::Array(..), x::anything ) option overload; A(ArrayNumElems(A)+1) := x end proc ] ):
Append([1,2],3);
1,2,3
Option overload can also be used to specify that a procedure exported by a package is only applied to arguments of specific type. If non-matching arguments are passed, the default behavior occurs instead.
For example, you can define a new implementation of `+` that works only on set arguments. The system default `+` operator is used for all other cases.
SetOperations := module() option package; export `+` := proc( a::set, b::set ) option overload; a union b end proc; end module:
with(SetOperations);
`+`
{1,2,3} + {4,5};
1,2,3,4,5
123 + 45;
168
For more information on packages, see Writing Packages.
The procname Option
As you will read later, the special name procname used within a procedure refers to the name by which the procedure was called. Among other things, this name is used to describe the location that an exception occurred when displaying an error message. It can also be used to return unevaluated calls to the procedure, and to make recursive calls.
If a procedure has the procname option, then the value of the procname special name within the procedure is inherited from the procedure that called it. If an error then occurs within the called procedure, the error is reported as having occurred in the calling procedure. This allows you, for example, to break up your procedure into sub-procedures, yet still have any errors reported as if they occurred in your main procedure.
For more information on the uses of procname, see Returning Unevaluated and Recursion.
The remember, cache, and system Options
The remember option activates a procedure's remember table. For a procedure with an active remember table, at the end of each invocation of the procedure, an entry that records the computed result for the specified arguments is made in the procedure's remember table. Subsequent calls to the procedure with the same arguments simply retrieve the result from the remember table instead of invoking the procedure.
The remember option allows writing an inherently recursive algorithm in a natural manner without loss of efficiency. For example, the Fibonacci numbers can be computed by the procedure:
Fibonacci := proc( n::nonnegint ) option remember; if n < 2 then n else Fibonacci(n-1) + Fibonacci(n-2) end if end proc:
Without the remember option, the time required to compute Fibonacci(n) is exponential in n. With option remember, the behavior becomes linear. For a comparison of the efficiency of this procedure with and without option remember, see Profiling a Procedure.
Entries can be explicitly inserted into a procedure's remember table by writing a function call on the left-hand side of an assignment. For example, the Fibonacci procedure can be written:
Fibonacci := proc( n::nonnegint ) option remember; Fibonacci(n-1) + Fibonacci(n-2) end proc:
Fibonacci(0) := 0:
Fibonacci(1) := 1:
A procedure's remember table can grow without bound, and for some procedures, may eventually contain many entries that will never be needed again. Adding the system option to a procedure allows Maple's garbage collector to clear out the remember table whenever garbage collection occurs. If a discarded result is needed again later, it will be recomputed.
As an alternative to remember tables, Maple also provides the cache option. Unlike a remember table, which can grow without bound, a cache has a maximum number of entries. When the cache becomes full, old entries are removed as new ones are inserted.
The cache option can be specified as just the symbol cache, or with an optional argument, in the form cache(N) where N is an integer specifying the size of the cache. If (N) is not specified, the cache is sized to hold 512 entries.
You can explicitly insert permanent entries into a procedure's cache using the Cache:-AddPermanent function.
When the interface variable verboseproc is 3, displaying a procedure also displays the contents of its remember table or cache as comments following the procedure definition:
Fibonacci(7);
13
interface(verboseproc=3):
print(Fibonacci);
procn::nonnegintoptionremember;Fibonacci⁡n − 1+Fibonacci⁡n − 2end proc#(0) = 0#(1) = 1#(2) = 1#(3) = 2#(4) = 3#(5) = 5#(6) = 8#(7) = 13
The remember and cache options are mutually exclusive, and the system option can only be used in conjunction with the remember option.
The trace Option
If a procedure is given the trace option, Maple will log each entry to and exit from the procedure, and the result of any assignment made during the execution of the procedure:
Fibonacci := proc( n::nonnegint ) option remember, trace; Fibonacci(n-1) + Fibonacci(n-2) end proc:
Fibonacci(3);
{--> enter Fibonacci, args = 3
{--> enter Fibonacci, args = 2
value remembered (in Fibonacci): Fibonacci(1) -> 1
value remembered (in Fibonacci): Fibonacci(0) -> 0
1
<-- exit Fibonacci (now in Fibonacci) = 1}
<-- exit Fibonacci (now at top level) = 2}
Variables in Procedures
A variable is a name representing an item of data, such as a numerical value, character string, or list of polynomials. The value of the variable, that is, which data item it represents, can change during the execution of a procedure (or sequence of Maple commands at the top level, outside of any procedure). There are three different classes of variables that can be used within a procedure: global, local, and lexically scoped.
Global Variables
A global variable has meaning within an entire Maple session. Many procedures may access a global variable, and all those procedures will refer to the same instance of that variable. A value assigned to a global variable during one function call will still be there the next time the procedure is called (if it was not changed by another procedure in the meantime).
Global variables are introduced by the global keyword, followed by one or more declarations:
global variableName := value, ... ;
The optional := value part is an assignment that is executed at the beginning of procedure execution. Semantically, it is equivalent to writing a separate assignment statement immediately after all the variable declaration clauses.
A global variable continues to exist and retain its value after the procedure exits, and conceptually, existed (and possibly had a value) before the procedure was executed. Its lifetime is thus the duration of the entire Maple session.
Local Variables
A local variable has meaning only within a particular procedure. If the same variable name is referenced outside of the procedure or within a different procedure, it refers to a different instance of that name, and is therefore a different variable.
The lifetime of a local variable is the time that the procedure is executing. The variable is created when the procedure is first invoked, and is usually discarded when the procedure has finished executing. If the same procedure is later executed again, a new instance of the variable is created. The variable does not retain its value from its previous lifetime.
Local variables are declared using the following syntax:
local variableName :: typeAssertion := initialValue, ... ;
The only required part of the declaration is variableName.
The optional :: typeAssertion assertion specifies that the variable is expected to always refer to values of the specified type. Since this is an assertion, if kernelopts(assertlevel) is set to 2, the type is checked every time a new value is assigned to the variable. If the value is not of the specified type, an exception is raised.
The optional := initialValue ensures that the variable is assigned the specified value before its first use. The initialValue can be any Maple expression. If the value is a literal expression sequence, it must be enclosed in parentheses, since otherwise the comma separating the elements of the sequence is interpreted as the comma separating individual variable declarations.
Lexically Scoped Variables
When one procedure is defined within another procedure (or within a module), variables in the outer procedure (or module) are visible to the nested procedure. This is called lexical scoping. Consider the following procedure, which given a list, produces a new list in which every element has been divided by the element with the largest magnitude, and then raised to a specified integer power:
PowerList := proc( L::list, power::integer ) local largest := max(abs~(L)); map( proc(x) (x / largest) ^ power end proc, L ) end proc:
PowerList([1,1/2,-3.14],2);
0.1014239929,0.02535599822,1.000000000
This example uses an anonymous nested procedure, declared directly within the expression that uses it. Notice that this inner procedure refers to both of the symbols power and largest. Because there are no variable or parameter declarations in the inner procedure that declare these symbols, lexical scoping ensures that they are automatically bound to the corresponding symbol in the outer procedure. In other words, power in the inner procedure refers to the parameter power of the outer procedure, and largest in the inner procedure refers to the local variable largest of the outer procedure.
Scoping Rules
If you want a variable to be local to a procedure or global, you should declare that variable using a local or global declaration. Declaring the scope of variables makes it easier to debug your code, and also makes it easier for someone else to understand your procedure.
On the other hand, if a variable is intended to refer to a parameter or local variable declared in an enclosing procedure, you must not declare it in the enclosed procedure. Doing so would defeat lexical scoping by making the variable local to the enclosed procedure, and thus a different variable with no connection to the one in the enclosing procedure.
If an undeclared variable does not correspond to a parameter or declared variable in a surrounding procedure, Maple determines its scope, and either automatically declare the variable as local or assume that it is global. When the variable is automatically declared local, such an implicit declaration generates a warning:
ImplicitLocal := proc( x, y ) z := x + y; if z < 0 then z^2 else z^3 end if end proc:
Warning, (in ImplicitLocal) `z` is implicitly declared local
Whether a variable is implicitly declared local or assumed to be global depends on how it is used:
If the variable appears on the left-hand side of an assignment statement or as the controlling variable of a for loop, Maple adds the variable to the procedure's local declarations. This means that if an enclosed procedure also refers to the variable (without declaration), lexical scoping binds it to the implicitly declared variable of the enclosing procedure. If a procedure in which such an implicit local declaration is displayed using the print function, the variable appears within the procedure's local declaration clause.
Otherwise, Maple assumes the variable is global. However, the variable is not added to the procedure's global declaration clause, which means that it is not subject to lexical scoping if the same name is used within an enclosed procedure.
Here is a summary of how the scope of a variable is determined:
If the variable is declared as a parameter, local, or global variable in the procedure in which the variable is encountered, the scope is specified by the declaration.
If the variable is not declared and there is a surrounding procedure (or module), the parameter, local (including implicit local), and global declarations of the surrounding procedure are examined. If the variable is found there, that binding is used. If it is not found, the search continues outward through the layers of surrounding procedures.
If the top level (outside of any procedure or module) is reached, the usage of the variable in the original procedure is examined. If it appears on the left-hand side of an assignment or as the controlling variable of a for loop, it is added to the procedure's local declarations. Otherwise it is assumed to be a global variable.
Non-Variable Name Bindings
In addition to the binding of names to parameters, local variables, and global variables, you can also explicitly bind other names to objects outside of the procedure with the uses clause:
uses bindingSpecification, ...
The uses keyword is followed by one or more bindings, in a form identical to those of the use statement, introduced in The use Statement. These bindings are in effect over the entire body of the procedure, in the same way they would be if the procedure body had been enclosed in a use statement.
The uses clause must appear at the top of the procedure body, together with the option, description, and initial local and global declarations. If you want to bind names in a subset of the procedure body, use a use statement instead.
The Statement Sequence
The statementSequence section of the procedure can contain any number of Maple statements, nested arbitrarily deeply. Other than one level evaluation and references to parameters, the semantics of statements within a procedure are the same as if those statements were executed outside of any procedure.
Referring to Parameters within the Procedure Body
When referring to parameters in the body of a procedure, there are some things to keep in mind.
Parameters Are Not Variables
Although a parameter declaration has a similar form to a local variable declaration, and parameters are referred to by name the same way that variables are, parameters are not variables. In Maple, a parameter always represents the argument that was bound to it.
Consider this example, which tries to use a parameter on the left-hand side of an assignment statement:
Add2 := proc( x, y ) x := x + y end proc:
Add2(3,4);
Error, (in Add2) illegal use of a formal parameter
This call to Add2 results in an error because the statement x := x + y is interpreted as 3 := 3 + 4. This is in contrast to languages such as C or C++, where a parameter is effectively a local variable that has been initialized to the argument value.
A parameter can be used on the left-hand side of an assignment if the value of the parameter is a name. The evaln parameter modifier can ensure that this is the case. Here is an example you saw earlier:
Here, the parameter r evaluates to the name `total`, an assignable object. Although it appears that an assignment to the parameter r is being made within the procedure, it is really the value of r, which in this case is the global variable total, that is being assigned to.
Required Parameters
Recall that a required parameter is one for which a corresponding argument must have been passed if the parameter is used during the execution of the procedure. Failure to pass an argument for a required parameter only raises an exception if an attempt is made to use that parameter during the particular invocation of the procedure.
For example, a procedure may determine, based on the value of its first required parameter, that it does not have to refer to the second required parameter.
Require := proc( x::integer, y::integer ) if x < 0 then x^2 else x * y end if end proc:
Require(-3);
Require(3,4);
12
Require(3);
Error, invalid input: Require uses a 2nd argument, y (of type integer), which is missing
Parameters with the seq Modifier
If a required (or optional) parameter was declared with the seq modifier, then the parameter will always have a value. That value will be a sequence of the specified type, a single item of that type, or NULL (or the default value for an optional parameter).
To do anything with a seq parameter other than pass it on to another procedure, you should convert the parameter value to a list and then work with the list:
AddAndMax := proc( x::seq(numeric) ) local a := 0, i; for i in [x] do a := a + i end do; a, max(x) end proc:
Without the [] brackets around x, this procedure would produce unexpected results if called with a single floating-point number. A for var in expr loop iterates over the operands of expr. If expr is a sequence of two or more numbers, it works as expected, but if expr were a single float, the loop would iterate over the floats operands (the significand and exponent). By enclosing x in a list, the loop will always iterate over the arguments bound to x.
Parameters with the uneval or evaln Modifiers
Parameters declared with the uneval or evaln modifiers are used like any other. Because Maple uses one level evaluation rules inside procedures, these parameters do not evaluate any further than they did when the arguments were initially evaluated. The eval function can be used to evaluate such parameters further.
Optional and Expected Ordered Parameters
Both optional and expected ordered parameters are always declared with default values, so using such a parameter within a procedure always yields a value. If an argument was bound to the parameter during procedure invocation, the parameter's value is that argument. Otherwise, the value of the parameter is the declared default value.
Keyword parameters also have declared default values, so using the parameter always yields a value. Unlike ordered parameters, keyword parameters receive their values from arguments of the form keyword=value. The value of a keyword parameter is the value portion of such an argument, not the entire argument.
Special Sequences for Referring to Parameters and Arguments
Maple provides a number of special named expression sequences to make it easy to work with parameters and arguments. These are useful in cases when it would be awkward if they could only be referred to by name.
The special names _params and _nparams can be used within a procedure to refer to the current values of the positional and ordered parameters. The _params symbol represents an expression sequence with _nparams members, one corresponding to each declared parameter (excluding keyword parameters). For a given procedure, _nparams is constant.
The _params symbol can only be used when immediately followed by an index enclosed in square brackets, _params[indexExpr]. It cannot be used in any other context. indexExpr can evaluate to one of the following:
An integer, N, in the range 1 to _nparams, or -_nparams to -1. This is just the selection operation on the sequence _params. It yields the value of the Nth parameter when N > 0, or the (_nparams+1+N)th parameter when N < 0 (negative integers index _params from the end instead of the beginning). If no argument was passed for the requested parameter and no default was declared, the result is NULL.
A range of such integers. This yields an expression sequence of values, with any NULL values omitted. A sequence of all the non-NULL positional and ordered parameter values can be obtained using _params[..]. Note that due to elision of NULLs, this could produce fewer than _nparams values.
An unevaluated parameter name. The notation _params['parameterName'] is equivalent to just writing parameterName, except when referring to a required positional parameter that was not bound to an argument. In that case _params['parameterName'] yields NULL whereas referring directly to parameterName would raise an exception.
The following example multiplies or divides the last three positional parameters by the first, depending on the value of the keyword parameter multiply:
MulDiv := proc( a, b, c, d, { multiply := true } ) if multiply then _params[-3..] * a else _params[-3..] / a end if end proc: MulDiv(100,1,2,3); MulDiv(100,1,2,3,multiply=false);
100,200,300
1100,150,3100
Just as _params and _nparams can be used to work with positional and ordered parameters in a flexible manner, _options and _noptions provide similar facilities for working with keyword parameters (often called keyword options).
The _options symbol represents an expression sequence containing _noptions members, one for each declared keyword parameter. Each member of _options is an equation of the form keyword=value.
If a keyword parameter was declared with multiple spellings, the corresponding member of _options uses the first spelling.
Unlike _params, _options can be used directly, not only through the selection of members of the sequence. Because _options returns a sequence of equations, even a member corresponding to an argument with a NULL value is non-NULL. It is an equation of the form keyword=NULL.
When _options is used with an index, the index must evaluate to an integer (or a range of integers) in the range 1 to _noptions or -(_noptions) to -1, or the unevaluated name of a keyword parameter.
The order of the equations in _options does not necessarily correspond to the order in which the keyword parameters were declared. Instead, the equations are in lexicographic order by keyword (the first spelling for keyword parameters with multiple spellings). This is the same order in which the keyword parameters are printed when the procedure is displayed by the print command. As a consequence of this, if a new keyword parameter is added to the procedure definition, the numeric index of the _options entry corresponding to a particular keyword parameter could change. Thus, when indexing _options, it is safest to use the _options['parameterName'] form.
The following example uses _options to pass all the keyword arguments on to another procedure:
MyRanMat := proc( a::integer, {density::float := 1.0, generator := 0..0.5} ) LinearAlgebra:-RandomMatrix(a, _options) end proc:
MyRanMat(2, density=0.75, generator=1..9);
MyRanMat(3, density=0.88);
The next example selects specific keyword arguments to pass to another procedure:
MulRanMat := proc( a::integer, {density::float := 1.0, generator := 0..0.5, mult := 1.0} ) mult * LinearAlgebra:-RandomMatrix(a, _options['density'], _options['generator']) end proc:
MulRanMat(4, density=0.75, generator=1..9, mult=x/2);
When there are more arguments in a function call than needed to match the called procedure's parameters, you can access the remaining arguments inside the procedure by using the special sequence _rest. The number of members in this sequence is given by _nrest.
Because these extra arguments do not correspond to any declared parameters, it is not possible for such an argument to have a NULL value. Recall that the only way for a parameter to be NULL is for no argument to have matched a parameter with no declared default value (or a default value of NULL). Since there is no declared parameter corresponding to any value in _rest, these conditions cannot hold.
This example uses _rest and _nrest to return the number of entries in a sequence of numbers, together with the maximum, and optionally the mean:
MaxMean := proc( {mean := false}) if mean then _nrest, max(_rest), Statistics:-Mean([_rest]) else _nrest, max(_rest) end if end proc:
c := MaxMean(6,200,400, mean=true);
c≔3,400,202.
All of the arguments that were passed to a procedure can be accessed using the special sequence _passed, having _npassed elements.
Prior to Maple version 10, _passed and _npassed were known as args and nargs. These older names are still accepted as synonyms for the newer names for backwards compatibility. Of historical interest, the earliest versions of Maple did not support declared parameters at all; args and nargs were the only mechanism for processing arguments.
The _passed sequence can be used to do explicit argument processing within the body of the procedure, although this is discouraged for two reasons:
Most argument processing requirements can be handled using the mechanisms described so far in this chapter. Doing so is usually significantly faster (in terms of both execution time and development time) than performing the same operations using your own custom argument processing algorithms within the procedure.
When special argument processing requirements do arise, it is often easier to work with _params, _options, and _rest. In many cases, the provided mechanisms can handle most of the processing, and it is only necessary to look at _rest to handle additional arguments.
The clearest and most efficient way to write a procedure to find the maximum of an arbitrary sequence of numbers is to use a single parameter with a seq modifier, and pass that parameter directly to Maple's built-in max function. However, the following example uses _passed, _npassed, and a for loop instead for demonstration purposes:
Maximum := proc( ) local max := _passed[1], i; for i from 2 to _npassed do if _passed[i] > max then max := _passed[i] end if end do; max end proc:
Care must be taken when the _options, _rest, or _passed sequences contain only a single entry and that sequence is assigned to a variable (for example, myOpts := _options). The variable will receive the value of that single element rather than an expression sequence. The safest way to use these expression sequences is to transform them into lists (for example, myOpts := [_options]).
6.6 How Procedures Are Executed
When a procedure definition is entered in Maple or read from a file, Maple does not execute the procedure. It does however translate the procedure into an internal representation, process all the parameter and variable declarations, perform lexical scoping and implicit local declaration, and simplify the procedure's statementSequence.
Automatic simplification of statementSequence is similar to simplification of expressions when Maple is used interactively, with a few exceptions. Consider the following procedure:
f := proc(x) local t := x + 3 + 0/2; if true then sqrt(x * 2.0 / 3.0) else t^2 end if end proc;
f≔procxlocalt;t ≔ x+3;sqrt⁡x*2.0/3.0end proc
During automatic simplification, the division 0/2 has been removed (because it does not contribute to the sum). More significantly, the entire if...then...else...end if statement has been replaced by just the body of the first branch, since the if-condition is true.
Notice that the expression sqrt(x * 2.0 / 3.0) has not been simplified to .8164965809*x^(1/2) as it would have been if entered at the top level, outside of a procedure. If this simplification had been performed, then the result produced by the procedure would depend on the setting of Digits (and other aspects of the floating-point environment) both when the procedure was simplified, and a possibly different setting of Digits when the procedure is later executed. By not performing any floating-point arithmetic during procedure simplification, the procedure will depend only on the state of the floating-point environment at execution time.
A procedure is executed after it has been invoked by a function call. Generally, the process is:
A function call, of the form functionName(functionArguments) is encountered during evaluation of an expression, either at the interactive level or while executing another procedure.
The functionName is examined to see if has been assigned a procedure.
The functionArguments are evaluated, usually from left to right.
The evaluated arguments are bound to the parameters of the procedure.
All of the procedure's local variables are instantiated. That is, for each local variable, a unique instance of the variable's name is created, with no prior value.
Interpretation of the procedure's statementSequence begins.
Interpretation continues until the last statement has been executed, an exception is raised (either as the result of an operation, or by an explicit error statement), or a return statement is encountered.
Binding of Arguments to Parameters
Argument processing occurs when a function call results in the invocation of a procedure. First, all the arguments are evaluated (except those corresponding to parameters with the uneval or evaln modifiers), and then they are matched to the parameters of the procedure.
Binding of Keyword Arguments
Keyword arguments are always matched first unless the procedure has parameters declared with the uneval or evaln modifiers. Maple makes a pass through the entire sequence of arguments looking for keyword=value equations where the keyword matches a declared keyword parameter of the procedure.
Whenever a matching keyword parameter is encountered, the right-hand side of the equation becomes the value for that parameter, and the equation is removed from further consideration as an argument. If more than one keyword argument matches a keyword parameter, only the last one takes effect.
Keyword parameter names (the keyword part) are Maple symbols like any other. If that symbol is in use as a variable, then using it in a keyword argument may not work as expected since the variable may evaluate to its value. To ensure that this does not happen, it is best to always use unevaluation quotes around the keyword part of a keyword argument:
f := proc( x::integer, { y::integer := 1 }, $ ) x * y end proc:
y := sin(z):
f(3,y=2);
Error, invalid input: too many and/or wrong type of arguments passed to f; first unused argument is sin(z) = 2
f(3,'y'=2);
6
This is a good practice when calling any function, whether it is a procedure you defined or a Maple command. See Protecting Names and Options.
When calling a procedure that accepts a keyword argument from within another procedure that has a parameter with the same name as the keyword argument, you must use both unevaluation quotes and the scope resolution operator, :-, to ensure that (the global instance of) the name itself is used instead of the value of the parameter:
g := proc( y::rational ) f(numer(y), ':-y'=denom(y)) end proc:
g(3/2);
If a keyword parameter has a declared parameterType for which true is a valid value (for example, the types truefalse or boolean), the keyword name alone is interpreted as a synonym for keyword=true.
f := proc( x::integer, { square::truefalse := false } ) if square then x^2 else x end if end proc:
[ f(2), f(3,square=true), f(4,square) ];
2,9,16
If a keyword parameter's keyword is a symbol of the form `symbol[symbol]` or `symbol[integer]`, the parameter is treated specially at during argument processing. Although such a keyword is still a symbol (because of the enclosing left single quotes), it matches indexed name keyword arguments. Specifically, if an equation whose left-hand side is an indexed name of the form symbol[symbol] or symbol[integer] is encountered, it matches the keyword parameter whose keyword symbol looks like the indexed name. For example, the keyword argument,
axis_label[1] = "time"
matches the keyword parameter:
`axis_label[1]` :: string := "x"
Keyword arguments with multiple indices are also recognized by attempting to match them using one index at a time. For example, the keyword argument,
axis_label[1,2] = ""
matches both of the keyword parameters,
`axis_label[1]` :: string := "x", `axis_label[2]` :: string := "y"
and sets them both to the empty string.
The following example illustrates these behaviors:
Indexed := proc( { `name[1]`::string := "hello", `name[2]`::string := "goodbye" } ) sprintf("name[1]=\"%s\" -- name[2]=\"%s\"",`name[1]`,`name[2]`) end proc:
Indexed(name[1]="hi");
name[1]="hi" -- name[2]="goodbye"
Indexed(name[1]="bonjour",name[2]="aurevoir");
name[1]="bonjour" -- name[2]="aurevoir"
Indexed(name[1,2]="good day");
name[1]="good day" -- name[2]="good day"
Indexed(name[2]=42);
Error, invalid input: Indexed expects value for keyword parameter name[2] to be of type string, but received 42
The Special Case of evaln and uneval Modifiers
There is one case in which the first stage of argument processing is not keyword matching. If the procedure was declared with any parameter(s) having an uneval or evaln modifier, arguments are first assigned to positional parameters from left to right until the rightmost uneval or evaln parameter has been bound to an argument or until all the arguments have been exhausted, whichever happens first. For each argument/parameter pair:
If the parameter has no parameterType, the argument matches trivially, and becomes the value for that parameter.
If the parameter has a parameterType specification, the argument may or may not match. If it matches, the argument becomes the value for that parameter. If it does not match, an exception is raised.
Accumulate := proc( r::evaln(numeric), n::numeric, { operation::symbol := `+` } ) r := operation(eval(r),n) end proc:
total := 0:
Accumulate(total, 2.3);
2.3
Accumulate(total, operation=`*`, 10);
23.0
Accumulate(operation=`*`, total, 100);
In the last call, an exception is raised because the first argument does not evaluate to a name.
Binding of Arguments to Positional and Ordered Parameters
After all arguments matching keyword parameters have been processed, matching of required positional and optional or expected ordered parameters is carried out. If any parameter had an uneval or evaln modifier, all parameters up to the rightmost of these will already have received arguments, so further matching begins with the next positional or ordered parameter after that.
Matching is done by traversing the parameter declarations from left to right. As each parameter is examined, an attempt is made to match it to the next unused argument as follows:
If the parameter has parameterType, but no defaultValue, the argument may or may not match. If it matches, the argument becomes the value for that parameter. If it does not match, an exception is raised.
If the parameter has both parameterType and defaultValue, the argument may or may not match. If it matches, the argument becomes the value for that parameter. If it does not match, the parameter receives its default value, and the argument remains available for matching a subsequent parameter.
In last two cases above, if the parameter's type uses the seq modifier, Maple continues to match additional arguments against the parameter until one is encountered that is not of the correct type. A seq parameter never results in an exception, because even if no arguments match, a valid sequence has been produced (the empty sequence).
At the end of this process, if there are any arguments left over, they are either put into the _rest sequence, or, if the procedure was declared with the end-of-parameters marker, $, an exception is raised.
Conversely, if all the arguments were bound to parameters, but there are parameters remaining to be assigned values, these receive their default values if they have one. Otherwise, they have no value, and attempting to use them (by name) within the procedure raises an exception.
Statement Sequence Interpretation
After all the arguments in a function call have been successfully bound to the procedure's parameters, Maple begins interpreting the procedure's statement sequence. Each statement is examined in turn and the necessary actions carried out.
For example, an assignment statement is interpreted by evaluating the right-hand side (the expression to be assigned), and resolving the left-hand side (the target of the assignment). The latter involves evaluating any indices if the left-hand side contains indexed names. Finally, the value of the right hand side is assigned to the resolved variable on the left-hand side.
When an if-statement is encountered, Maple evaluates the condition. If it is true, statement sequence interpretation continues with the first statement within the first branch of the if-statement. When the statements within that branch have all been executed, interpretation continues with the first statement after the end if. If if-condition was false, Maple looks for an elif or else branch and continues in a similar manner.
When there are no further statements remaining, Maple behaves as if a return statement had been encountered.
Variable Evaluation Rules within Procedures
Maple fully evaluates global variables whenever they are referenced, even within procedures, but local variables are evaluated in a special way. When a local variable is encountered during procedure execution, it is evaluated only one level. Consider the following Maple statements, outside of any procedure:
f := x + y;
f≔x+y
x := z^2 / y;
x≔z2y
z := y^3 + 3;
z≔y3+3
Since these statements undergo normal full recursive evaluation, the following result is returned:
f;
y3+32y+y
The same sequence of steps within a procedure would yield a different result:
OneLevelEval := proc( ) local f, x, y, z; f := x + y; x := z^2 / y; z := y^3 + 3; f end proc:
OneLevelEval();
x+y
The concept of one-level evaluation is unique to symbolic languages like Maple, where the value of a variable can be, or include, the name of another variable. One-level evaluation avoids arbitrarily deep computation at every step of a procedure and is thus important for efficiency. It has very little effect on the behavior of procedures, because most procedures have a sequential structure. When full evaluation of a local variable is required within a procedure, use the eval function:
FullEval := proc( ) local f, x, y, z; f := x + y; x := z^2 / y; z := y^3 + 3; eval(f) end proc:
FullEval();
In addition to illustrating one level evaluation, this example also introduces the idea of an escaped local. The expression returned by OneLevelEval is x + y and contains the symbols x and y. However, these are not the global variables of the same names; they are the local x and y declared in OneLevelEval. Because these variables have escaped, they continue to exist beyond their normal lifetime even though the procedure has finished executing. Usually, an escaped local indicates a programming error such as forgetting to assign a value to a local variable before using it. There are situations where letting a local escape can be useful, such as generating unique instances of a name that will be guaranteed never to evaluate further.
Returning Values from a Procedure
When a procedure has finished executing, a value is returned. If the procedure was invoked by a function call, possibly within a larger expression, the returned value is used as the value of that function. At the interactive level, the returned value is displayed (unless the input was terminated by a colon instead of a semicolon).
Except when a procedure raises an exception, a value is always returned. In the absence of an explicit return statement, the returned value is the value of the last statement executed in the procedure. The value of a statement means:
The value computed by the right-hand side of an assignment statement.
The value of the expression when the statement is an expression.
The value of the last statement executed within the branches of an if statement or within the body of a loop.
Note that NULL is a valid expression (and thus a valid statement). A procedure that returns NULL is still returning a value, although at the interactive level, nothing is displayed.
You can use an explicit return statement to end the execution of the procedure and return a value immediately:
return expression;
Upon encountering a return statement during execution, Maple evaluates the expression, and then immediately terminates the execution of the procedure, with the result of the evaluation as the returned value.
This example uses an explicit return statement to immediately return the position i of a value x in a list when the value is found. If the value is not found, the procedure returns 0:
Position := proc( x::anything, L::list ) local i; for i to numelems(L) do if x = L[i] then return i end if end do; 0 end proc:
Position(3, [2,3,5,7,1,3,7,9,3,9]);
Position(4, [2,3,5,7,1,3,7,9,3,9]);
0
The following procedure computes the greatest common divisor, g, of two integers a and b. It returns the expression sequence g, a/g, b/g. The case a = b = 0 is treated separately because in that case, g is zero:
GCD := proc( a::integer, b::integer, $ ) local g; if a = 0 and b = 0 then return 0, 0, 0 end if; g := igcd(a,b); g, iquo(a,g), iquo(b,g) end proc:
GCD(0,0);
0,0,0
div, quo1, quo2 := GCD(12,8);
div,quo1,quo2≔4,3,2
This example illustrates that you can return a sequence of values from a procedure, and that those values can then be assigned to a sequence of names by the caller. Whenever a procedure returns a sequence of values, the result can be assigned to a sequence of the same number of names (a multiple assignment). If you assigned the result to a single name, then the value of that name would be the entire sequence.
Sometimes, it is convenient to write a procedure which will return a different number of values depending on the context in which it was called. A procedure can use the special variable _nresults to determine how many results are expected by the caller. Here is a version of the previous procedure that returns only a single result when called from within an arithmetic expression (the tests for the case a = b = 0 has been omitted for brevity):
GCD := proc( a::integer, b::integer, $ ) local g := igcd(a,b); if _nresults = 1 or _nresults = undefined then g else g, iquo(a,g), iquo(b,g) end if end proc:
div := GCD(12,8);
div≔4
GCD(12,8) ^ 2;
16
{ GCD(12,8) };
4
The _nresults variable has the value undefined if the procedure was called from within an expression or within the arguments of another function call. It has an integer value if the call was from the top level of an expression appearing on the right-hand side of an assignment. The value of _nresults is the number of variables on the left-hand side of the assignment statement.
Do not use _nresults in a procedure with the remember or cache options. Only the first computed result is stored in the remember table or cache. Subsequent calls with the same input but a different number of expected results will not return the expected number of results. (The Cache package can be used to manually implement a simulated remember table that works correctly in conjunction with _nresults.)
Another alternative for returning more than one value from a procedure is to assign values to variables whose names were passed in as values. The following procedure determines whether a list L contains an expression of type T. If found, the procedure returns the index of the (first matching) expression. If the procedure is called with a third argument, then it also assigns the expression to that name.
FindType := proc( T::type, L::list, V::evaln, $ ) local i; for i to numelems(L) do if L[i] :: T then if _npassed = 3 then V := L[i] end if; return i end if end do end proc:
FindType(string, [2,3/4,"Hello",x+y]);
3
FindType(string, [2,3/4,"Hello",x+y], s);
s;
Hello
When FindType was called with two arguments, the procedure just returned the index of the found list element.
When called with three arguments, parameter V received the name, not the value of global variable s. The evaln declaration of V ensures that V will always refer to a name. Just before returning, the procedure assigned the found expression to s, as referenced by V.
If, during the execution of the procedure, you need to refer to the value that has been assigned to a name via an evaln parameter, enclose such references to the parameter within a call to eval:
Returning Unevaluated
If a procedure cannot perform a requested computation, it can return the unevaluated form of the function call that invoked it. For example, the procedure below computes the larger of two values if it can, or returns unevaluated if it cannot:
Larger := proc( x, y ) if x :: numeric and y :: numeric then if x > y then x else y end if else 'Larger'(x,y) end if end proc:
Larger(3.2, 2);
3.2
r := Larger(a, 2*b);
r≔Larger⁡a,2⁢b
The unevaluation quotes around Larger within the procedure specify that the function call expression will be constructed, but no procedure invocation will take place (therefore this is not a recursive call).
The returned unevaluated function call can later be re-evaluated. If a and b have numeric values at that time, Larger will return a number, otherwise it will return unevaluated once again.
a, b := 3, 2;
a,b≔3,2
r;
Because of one level evaluation, the last line in the example above would have to be written as r := eval(r) if r were a local variable in a procedure.
Rather than using the procedure's name to construct an unevaluated function call to return, you can also use the special name procname. The statement, 'Larger'(x,y) could have been written 'procname'(x,y). The advantage to using procname is that such unevaluated returns are immediately apparent to anyone reading the source code of your procedure.
Note that if your procedure was called from within another procedure and has the procname option, then an unevaluated call of the form 'procname'(x,y) refers to the procedure that invoked your procedure.
By writing procedures to return unevaluated when it is not possible to carry out the computation, you make it easier for the user of the procedure to use it in contexts where otherwise it would produce an error:
plot( Larger(x, 1/x), x = 1/2 .. 2 );
int( Larger(x, 1/x), x = 0.25 .. 2.0 );
2.886294361
If Larger had been implemented without the unevaluated return, both of the above commands would have failed because the first argument to plot and int could not have been evaluated:
LargerNoUneval := proc( x, y ) if x > y then x else y end if end proc:
plot( LargerNoUneval(x, 1/x), x = 1/4 .. 2 );
Error, (in LargerNoUneval) cannot determine if this expression is true or false: 1/x < x
int( LargerNoUneval(x, 1/x), x = 0.25 .. 2.0 );
Many Maple functions use the technique of returning unevaluated. For example, the sin and int functions return a result when they can, or return unevaluated when it is not yet possible to compute a result.
6.7 Using Data Structures with Procedures
The choice of appropriate data structures to solve a particular problem has already been discussed in Basic Data Structures, but it is worth keeping in mind how your procedure might be used by you or others in the future. If the problem you are solving involves a small amount of data, you may have been tempted to choose a data structure without regard to efficiency or scalability when writing your procedure. If the procedure is used later to solve a larger problem, it may not be able to handle the problem in a reasonable amount of time or memory if you chose a data structure only suitable for small problems.
Passing Data Structures to Procedures
Traditional procedural programming languages such as Pascal or C usually pass arguments to procedures by value. This means that the procedure receives a copy of the data passed to it. Such languages also allow values to be passed by reference. Pascal does this by prefixing the parameter declaration with the var keyword. C requires that the parameter be declared as a pointer, using the * prefix, and that the caller explicitly pass the address of the argument using the & prefix (except when passing pointers to arrays).
Passing arguments by value ensures that the procedure cannot modify the passed data as a side-effect, but requires making a copy of the data. Passing by reference is more efficient for large data objects, but allows the procedure to (possibly unintentionally) modify the caller's copy of the data.
In Maple, data is always passed by reference, but the immutability of most data types ensures that the procedure cannot modify the caller's copy of the data. The exceptions are Maple's mutable data structures: tables, Arrays, Matrices, Vectors, records, and objects. Modifying these within a procedure will modify the caller's copy. Fortunately, these larger data structures are the ones that you would most often want to pass by reference, since copying such data consumes time and space.
A third argument passing convention seen in some programming languages is passing by name. In this case, instead of passing the value of a variable, the variable itself is passed. The called procedure can then assign a new value to the variable, which will remain in effect when the procedure returns to the caller. Maple allows passing by name via the evaln parameter declaration modifier, or by explicitly quoting the name when calling the procedure. This does not contradict the earlier statement that Maple always passes by reference, because it is now the variable name that is being passed by reference.
Returning Data Structures from Procedures
Just as values are always passed by reference, they are returned from procedures by reference, too. Thus, the cost in time and space of returning a large structure such as a list is not any more than that of a small piece of data like an integer.
When returning a table or procedure from a procedure, care must be taken to ensure that it is the data structure itself and not the name referring to it that is returned. This is because tables and procedures use last name evaluation.
IncorrectListToTable := proc( L :: list ) local T := table(), i; for i to numelems(L) do T[i] := L[i] end do; T end proc:
IncorrectListToTable(["String",123,Pi]);
T
The example above returns the local variable T instead of the actual table. Although the returned value can be used as if it were the actual table, every access to it involves an extra level of addressing behind the scenes, thus consuming more time.
ListToTable := proc( L :: list ) local T := table(), i; for i to numelems(L) do T[i] := L[i] end do; eval(T) end proc:
ListToTable(["String",123,Pi]);
table⁡1=String,2=123,3=π
Example: Computing an Average
A common problem is to write a procedure that computes the average of n data values x1, x2, ..., xn according to the following equation:
μ=∑i=1nxin
Before writing the procedure, think about which data structure and Maple functions to use. You can represent the data for this problem as a list. The numelems function returns the total number of entries in a list X, while the ith entry of the list is obtained by using X[i]:
X := [1.3, 5.3, 11.2, 2.1, 2.1];
X≔1.3,5.3,11.2,2.1,2.1
numelems(X);
X[2];
5.3
add( i, i=X );
22.0
Using these ideas, write the procedure Average which computes the average of the entries in a list. It handles empty lists as a special case:
Average := proc( L::list, $ ) local n := numelems(L), i, total; if n = 0 then error "empty list" end if; total := add(i,i=L); total / n end proc:
Using this procedure you can find the average of list X defined above:
Average(X);
4.400000000
The procedure also works if the list contains symbolic entries:
Average([a, b, c]);
a3+b3+c3
Calling Average with an empty list raises an exception:
Average([]);
Error, (in Average) empty list
A list is a good choice for the data in this example because the data is stored and used in a calculation, but the list itself does not need to be modified.
Example: Binary Search
One of the most basic and well-studied computing problems is that of searching. A typical problem involves searching a list of words (a dictionary, for example) for a specific word w. There are many possible methods. One approach is to search the list by comparing each word in the dictionary with w until either w is found, or the end of the list is reached. Study the code for procedure LinearSearch (the first attempt at solving this problem):
LinearSearch := proc( D::list(string), w::string ) local x; for x in D do if x = w then return true end if end do; false end proc:
Unfortunately, if the dictionary is large, this approach can take a long time. You can reduce the execution time required by sorting the dictionary before you search it. If you sort the dictionary into ascending order, then you can stop searching as soon as you encounter a word greater than w. On average, it is still necessary to search half the dictionary.
Binary searching provides an even better approach. Check the word in the middle of the sorted dictionary. Since the dictionary is already sorted, you can determine whether w is in the first or the second half. Repeat the process with the appropriate half of the dictionary until w is found, or it is determined not to be in the dictionary.
BinarySearch := proc( D::list(string), w::string ) local low := 1, high := numelems(D), mid; while low <= high do mid := trunc((low + high) / 2); if w < D[mid] then high := mid - 1 elif w > D[mid] then low := mid + 1 else return true end if end do; false end proc:
Dictionary := [ "induna", "ion", "logarithm", "meld" ];
Dictionary≔induna,ion,logarithm,meld
BinarySearch( Dictionary, "hedgehogs" );
BinarySearch( Dictionary, "logarithm" );
BinarySearch( Dictionary, "melody" );
Example: Plotting the Roots of a Polynomial
You can construct lists of any type of object, including other lists. A list that contains two numbers can represent a point in the plane, and a list of such list can represent several such points. The Maple plot command uses this structure to generate plots of points and lines.
plot([ [0, 0], [1, 2], [-1, 2] ], style=point, symbol=point, color=black);
You can make use of this to write a procedure that plots the complex roots of a polynomial. For example, consider the polynomial x3−1.
y := x^3-1;
y≔x3−1
First, find the roots of this polynomial. You can find the numeric roots of this polynomial by using fsolve. By enclosing the call to fsolve in square brackets, you create a list of the roots.
R := [ fsolve(y=0, x, complex) ];
R≔−0.5000000000−0.8660254038⁢I,−0.5000000000+0.8660254038⁢I,1.
Next, change this list of complex numbers into a list of points in the plane. The Re and Im functions return the real and imaginary parts of a complex number respectively. You can use the map function and an anonymous procedure to convert the entire list at once.
points := map(z -> [Re(z), Im(z)], R);
points≔−0.5000000000,−0.8660254038,−0.5000000000,0.8660254038,1.,0.
Finally, plot the resulting list.
plot(points, style=point, symbol=point, color=black);
You can automate the process by writing a procedure that follows the same sequence of steps. The input must be a polynomial in x with constant coefficients.
RootPlot := proc( p::polynom(constant,x) ) description "Plots the roots of a polynomial in x"; local R := [ fsolve(p, x, complex) ]; local points := map( z -> [Re(z), Im(z)], R ); plot(points, style=point, symbol=point, color=black) end proc:
Test the RootPlot procedure by plotting the roots of the polynomial x6+3⁢x5+5⁢x+10.
RootPlot( x^6+3*x^5+5*x+10 );
Generate a random polynomial using the randpoly function, and then test the RootPlot procedure again.
y := randpoly(x, degree=100);
y≔−56⁢x95−62⁢x42+97⁢x8−73⁢x5−4⁢x3
RootPlot( y );
6.8 Writing Usable and Maintainable Procedures
As with any programming language, it is easy to write a Maple procedure that others cannot easily comprehend (or that you, as the author, have trouble understanding when you look at it, or try to modify it, in the future). Maple's syntax provides you with several facilities to alleviate such problems and produce maintainable code.
Formatting Procedures for Readability
Although it is possible to enter an entire procedure on a single very long line, this makes it difficult to understand and edit. For example, the binary search procedure shown earlier could have been written this way:
Procedures are more easily readable if written with one statement per line, and with the statements enclosed within the bodies of loops and if-statements indented:
Sometimes, a single statement is too long to fit on a single line. Maple's syntax allows you to insert line breaks and white space between any two syntactic tokens such as reserved words, variable names, numbers, and punctuation. Indentation can be used within a statement to clarify the grouping of expressions. For example, the polynomial root plotting procedure could have been written like this:
RootPlot := proc( p::polynom(constant,x) ) description "Plots the roots of a polynomial in x"; plot(map(z -> [Re(z), Im(z)], [fsolve(p, x, complex)]), style=point, symbol=point, color=black) end proc:
In this version of RootPlot, the procedure body consists of a description and a single statement. The indentation makes it clear that z -> [Re(z), Im(z)] and [fsolve(p, x, complex)] are arguments of the call to map, and that the result of this call together with the style, symbol, and color options are the arguments of plot.
Commenting Your Code
Comments are one of the most important tools in writing maintainable code. There are two ways of writing comments in Maple procedures:
# Comment text until the end of the line.
(* Delimited comment text. *)
A # character anywhere within a procedure except inside a "string" or `quoted name` introduces a comment. Everything following # until the end of the line is considered to be a comment and is ignored by Maple. This form is useful for short comments of one or two lines, or to annotate a line.
Average := proc( ) # Compute total. local total := add(_passed[i],i=1.._npassed); # Divide total by number of values. total / _npassed; end proc;
Warning, (in Average) `i` is implicitly declared local
Average≔proclocaltotal,i;total ≔ add⁡args[i],i=1..nargs;total/nargsend proc
Comments enclosed in (* and *) can begin and end anywhere except within a "string" or `quoted name`. Everything between the delimiters is ignored by Maple. This form can be used within a line or to write a multiline comment.
BetterAverage := proc( ) (* This procedure computes the average of its arguments. It is an error if no arguments were passed. *) if _npassed = 0 then error "too few values" else add(_passed[i],i=1.._npassed) (*TOTAL*) / _npassed end if end proc;
Warning, (in BetterAverage) `i` is implicitly declared local
BetterAverage≔proclocali;ifnargs=0thenerrortoo few valueselseadd⁡args[i],i=1..nargs/nargsend ifend proc
Notice that comments are discarded by Maple when the procedure is simplified. Comments are purely for the benefit of the programmer(s) who write, read, and maintain the procedure.
As described earlier, a procedure in Maple can also have a description section. One or more strings can follow the description keyword. Like comments, these have no effect on the execution of the procedure, but they are retained when the procedure is simplified.
AnotherAverage := proc( ) description "Compute the average of one or more values.", "At least one value must be passed."; if _npassed = 0 then error "too few values" else add(_passed[i],i=1.._npassed) / _npassed end if end proc:
Warning, (in AnotherAverage) `i` is implicitly declared local
You can use Maple's Describe command to print a procedure's declared parameters, return type, and description.
Describe(AnotherAverage);
# Compute the average of one or more values. # At least one value must be passed. AnotherAverage( )
Describe(RootPlot);
# Plots the roots of a polynomial in x RootPlot( p::polynom(constant,x) )
6.9 Other Methods for Creating Procedures
Enclosing a sequence of statements in proc...end proc is not the only way to create a procedure in Maple. You can also use functional operator notation or the unapply function.
Functional Operators: Mapping Notation
Functional operator notation (or arrow notation) is a method by which you can create a special form of procedure which represents a mathematical function or mapping. The syntax is:
( parameterSequence ) -> expression
The parameterSequence can be empty, and the expression must be a single expression or an if statement.
F := (x,y) -> x^2 + y^2;
F≔x,y↦x2+y2
If the procedure requires only a single parameter, you can omit the parentheses around parameterSequence:
G := n -> if n < 0 then 0 else 1 end if;
G≔n↦ifn<0then0else1endif
Internally, a procedure created using operator notation is the same as any other procedure, except that it will have options operator, arrow. You can invoke such a procedure in the usual way:
F(1,2);
G(-1);
You can use declared parameter types when defining a functional operator:
H := ( n::even ) -> n! * (n/2)!;
H≔n::even↦n!⋅n2!
H(6);
4320
H(5);
Error, invalid input: H expects its 1st argument, n, to be of type even, but received 5
The arrow notation is designed for simple one-line function definitions. It does not provide a mechanism for specifying local or global variables, options, a description, or more than a single statement. If these are required, use the more general proc...end proc notation.
The unapply Function
Another way to create a procedure is with the unapply function:
unapply( expression, parameterSequence )
The expression must be a single expression, and parameterSequence a sequence of symbols.
B := x^2 + y^2;
B≔x2+y2
F := unapply(B, x, y);
F≔x,y↦y2+x2
F(3,4);
25
The functional operator notation (or arrow notation) is a syntax for writing an operator. The unapply function is a function mapping expressions to procedures. Use the unapply function to create a procedure from an expression that was computed instead of one that was entered. This works because unapply first evaluates the expression and then encloses the result in a procedure. The arrow notation always produces a procedure containing the expression that was entered.
IntExpr := int(1/(x^3+1), x);
IntExpr≔ln⁡x+13−ln⁡x2−x+16+3⁢arctan⁡2⁢x−1⁢333
IntFunc := unapply(evalf(IntExpr), x);
IntFunc≔x↦0.3333333333⋅ln⁡x+1.−0.1666666667⋅ln⁡x2−1.⋅x+1.+0.5773502693⋅arctan⁡1.154700539⋅x−0.5773502693
IntFunc(3.5);
0.8664586908
If you had tried to use operator notation to create the IntFunc procedure, you would not get what you expected:
BadIntFunc := x -> evalf(IntExpr);
BadIntFunc≔x↦evalf⁡IntExpr
BadIntFunc(3.5);
0.3333333333⁢ln⁡x+1.−0.1666666667⁢ln⁡x2−1.⁢x+1.+0.5773502693⁢arctan⁡1.154700539⁢x−0.5773502693
Notice that the result still contains the symbol x. This is because the x appearing in IntExpr is the global variable x, not the parameter x of BadIntFunc.
Anonymous Procedures
Recall from the beginning of this chapter that a procedure is a valid Maple expression, independent from any name that it may have been assigned to. You can in fact create, manipulate, and invoke a procedure without ever assigning it to name. Such procedures are called anonymous.
Consider the following mapping (a procedure in functional operator notation):
x -> x^2;
x↦x2
You can invoke this anonymous procedure in the following manner:
(x -> x^2) (t);
t2
Syntactically, this is a Maple function call. Instead of specifying the procedure to call by giving its name, the procedure is given directly. The same method can be used to directly call a procedure defined using the proc...end proc notation:
proc( x, y ) x^2 + y^2 end proc (u, v);
u2+v2
Anonymous procedures are often used with the map function:
map( x -> x^2, [1,2,3,4] );
1,4,9,16
They are also used to initialize Arrays in Arrays. You can find numerous other examples of anonymous procedures in this guide.
Procedures, whether anonymous or not, can be combined in expressions, or processed by operators such as D, the differential operator:
D( x -> x^2 );
x↦2⋅x
F := D( exp + 2 * ln );
F≔exp+2⁢z↦1z
F(x);
ⅇx+2x
6.10 Recursion
A procedure is termed recursive if it contains a call to itself, either directly, or indirectly through another procedure that it calls. In order for a recursive procedure to produce a result, it must test for some condition under which the recursion terminates. Otherwise, it would go on calling itself forever (until Maple runs out of stack space).
You have already seen one example of recursion used to compute Fibonacci numbers in The remember, cache, and system Options. Another well-known example of recursion is the computation of the factorial of an integer. For any integer 0<n, the factorial (denoted by n!) is defined by n!=n⁢n−1!. For n=0, n! is defined to be equal to 1. This definition naturally lends itself to a recursive implementation:
Fact := proc( n::nonnegint, $ ) if n > 0 then n * Fact(n-1) else 1 end if end proc;
Fact≔procn::nonnegint, $if0<nthenn*Fact⁡n − 1else1end ifend proc
Fact(0);
Fact(4);
24
Fact(-4);
Error, invalid input: Fact expects its 1st argument, n, to be of type nonnegint, but received -4
The if-statement ensures that Fact only calls itself when 0<n.
Rather than using the name to which the procedure has been assigned to make the recursive call, you can also use procname or thisproc. This ensures that the recursion continues to work even if the procedure body is later assigned to a different name. The special symbol procname refers to the name that the procedure was called with. In the Fact example, procname would be equivalent to Fact. The symbol thisproc on the other hand refers to the procedure itself. Calling the procedure recursively using thisproc is slightly more efficient, and works within anonymous procedures.
This example uses an anonymous version of the Fact procedure above to compute the factorials of a list of numbers:
map( n -> if n > 0 then n * thisproc(n-1) else 1 end if, [0, 1, 2, 3, 4] );
1,1,2,6,24
The BinarySearch procedure you saw earlier also lends itself to a recursive implementation.
BinarySearch := proc( D::list(string), w::string, low::integer := 1, high ::integer := numelems(D) ) local mid; if low > high then # Nothing left to search. Word is not in list. false else mid := trunc((low + high) / 2); if w < D[mid] then # Search within the left part of the range. thisproc(D,w,low,mid-1) elif w > D[mid] then # Search within the right part of the range. thisproc(D,w,mid+1,high) else # Word was found in middle of current range. true end if end if end proc:
You use this procedure by passing it a sorted list of strings and a word to search for. The two optional parameters, low and high, specify which range of list elements to search and have default values specifying the entire list. After determining that the word is lexicographically less than or greater than the middle value, this procedure calls itself recursively, passing the list and word, as well as appropriate values for the low and high parameters to restrict the search. The recursion (and thus the search) ends when the procedure is asked to search a zero-length section of the list (in which case the word was not found), or when the middle element of the specified range contains the word.
If your procedure has the procname option, any attempt to make a recursive call via procname instead of thisproc calls the procedure that invoked your procedure.
6.11 Procedures That Return Procedures
Some of the built-in 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 too. This section discusses how values are passed from the outer procedure to the inner procedure.
Example: Creating a Newton Iteration
The following example demonstrates how locating the roots of a function by using Newton's method can be implemented in a procedure.
To use Newton's method to find the roots of a function graphically:
Choose a point on the x-axis that you think might be close to a root.
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 the initial guess. Use the new point as a new guess and repeat this process.
The same process can be expressed numerically as an iteration:
xk+1=xk−f⁡xkf'⁡xk
where x0 is the initial guess, and xk is the result of the kth iteration.
The following procedure takes a function and creates a new procedure which expects an initial guess and, for that particular function, generates the next guess. The new procedure is specific to the function that it was generated for, and does not work for other functions. To find the roots of a new function, use MakeIteration to generate a new iterating procedure.
MakeIteration := proc( expr::algebraic, x::name ) local 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⁢x:
expr := x - 2 * sqrt(x);
expr≔x−2⁢x
iter := MakeIteration(expr,x);
iter≔x↦x−x−2⋅x1−1x
The generated procedure, which is assigned to iter, returns the solution, x=4 after a few iterations.
x0 := 2.0:
to 4 do x0 := iter(x0); print(x0) end do:
4.828427124
4.032533198
4.000065353
4.000000000
Observe that the MakeIteration procedure above requires its first argument to be an algebraic expression. You can also write a version of MakeIteration that works on other procedures (such as functional operators).
MakeIteration := proc( f::procedure ) (x->x) - eval(f) / D(eval(f)); end proc:
This example uses Maple's ability to treat expressions containing procedures as procedures. The result of calling this version of MakeIteration is an expression with procedures as operands (x->x is just a procedure that maps any value to itself).
Because of last name evaluation, MakeIteration will accept either a procedure or a name whose value is a procedure. The calls to eval within MakeIteration ensure that the result refers to the actual procedure that was passed in, instead of to the name of that procedure.
g := x -> x - cos(x);
g≔x↦x−cos⁡x
iter := MakeIteration(g);
iter≔x↦x−x↦x−cos⁡xx↦1+sin⁡x
Note that the procedure generated by the call to MakeIteration is independent of the name g (because of the aforementioned calls to eval). Thus, you can later change g without breaking iter. You can find a good approximate solution to x−cos⁡x=0 in a few iterations.
x0 := 1.0;
x0≔1.0
0.7503638679
0.7391128909
0.7390851334
0.7390851332
Example: 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 like this:
ShiftLeft := ( f::procedure ) -> ( x -> f(x+1) ):
Try performing a shift on sin(x).
ShiftLeft(sin);
x↦sin⁡x+1
Maple lexical scoping rules declare the f within the inner procedure to be the same f as the parameter of the outer procedure. Therefore, the ShiftLeft procedure works as written.
The previous example of ShiftLeft works with univariate functions but it does not work with functions of two or more variables.
h := (x,y) -> x*y;
h≔x,y↦y⋅x
hh := ShiftLeft(h);
hh≔x↦h⁡x+1
hh(x,y);
Error, (in hh) invalid input: h uses a 2nd argument, y, which is missing
To modify ShiftLeft to work with multivariate functions, rewrite it to generate procedures that accept the additional parameters and pass them on to f.
ShiftLeft := ( f::procedure ) -> ( x->f(x+1, _rest) ):
hh≔x↦h⁡x+1,_rest
y⁢x+1
Because the ShiftLeft procedure does not call eval on parameter f, the function hh depends on h. Changing the value assigned to h implicitly changes hh:
h := (x,y,z) -> y*z^2/x;
h≔x,y,z↦y⋅z2x
hh(x,y,z);
y⁢z2x+1
6.12 The Procedure Object
Recall that a Maple procedure is itself an expression in Maple which can be (and usually is) assigned to a name. Like any Maple expression, a procedure has a type, and has operands (not to be confused with its parameters).
The procedure Type
Maple recognizes all procedures (and names to which a procedure has been assigned) as being of type procedure. To verify whether a name or an expression is a procedure, use the type function or :: operator:
F := proc( x ) x^2 end proc:
type(F, name);
type(F, procedure);
type(F, name(procedure));
type(eval(F), name);
type(eval(F), procedure);
The procedure type is a structured type (see Structured Types). Using a structured type allows you to verify that a name refers to a procedure, and additionally verify the specified types of the procedure's parameters.
G := proc( n::integer, s::string ) print(s); 2 * n * length(s) end proc:
type(G, procedure(integer,string));
Procedure Operands
Every Maple procedure has eight operands, corresponding to sub-parts of the procedure definition. The following table lists each operand and the corresponding op call that can be used to access it. In the table, the name P represents the name of the procedure, and the eval call is necessary so that op will be passed the procedure, not the name (because procedures have last name evaluation).
Operand
op Command
Parameters
op(1,eval(P))
All local variables
op(2,eval(P))
op(3,eval(P))
Remember table
op(4,eval(P))
op(5,eval(P))
Declared global variables
op(6,eval(P))
Lexical scoping table
op(7,eval(P))
Return type
op(8,eval(P))
The value of any operand can be a single item, an expression sequence if there are two or more items (such as local variables), or NULL if there were no items (for example, no options).
The lexical scoping table is an internal structure that records the correspondence between undeclared variables within the procedure and locals (or exports), globals, and parameters of surrounding procedures (or modules). It does not correspond to any part of the procedure as written.
The procedure's statement sequence is not one of the operands of the procedure, and thus cannot be extracted by op. This is because statements and statement sequences are not expressions, and thus cannot be assigned to names or otherwise manipulated.
The following nested procedure illustrates how the parts of the procedure map to the operands. Note that this example refers to the procedure that appears within, and is returned by procedure MakeProc (in order to illustrate lexical scoping). This procedure is not intended to illustrate good programming style, but merely provide an example showing all the possible operands.
MakeProc := proc( offset::integer ) description "Create and return a procedure"; proc( n::integer, s::string ) :: integer; description "An example to illustrate procedure operands"; option remember; global codes := convert(s,bytes); local i; total := 0; for i to nops(codes) do total := total + codes[i] end do; total * n + offset end proc: end proc:
Warning, (in anonymous procedure within MakeProc) `total` is implicitly declared local
P := MakeProc(3):
P; # The name of the procedure
P
eval(P); # The procedure
procn::integer,s::string::integer;optionremember;locali,total;globalcodes;descriptionAn example to illustrate procedure operands;codes ≔ convert⁡s,bytes;total ≔ 0;foritonops⁡codesdototal ≔ total+codes[i]end do;total*n+3end proc
op(1,eval(P)); # Parameters
n::ℤ,s::string
op(2,eval(P)); # All local variables
i,total
op(3,eval(P)); # Options
remember
P(3,"nonsense"); # Place an entry in the remember table
2622
op(4,eval(P)); # Show the remember table
table⁡3,nonsense=2622
op(5,eval(P)); # Description
An example to illustrate procedure operands
op(6,eval(P)); # Declared global variables
codes
op(7,eval(P)); # Lexical table
offset,3
op(8,eval(P)); # Return type
integer
6.13 Exercises
Implement the function f⁡x=−x2+13−1, first as a procedure, and then by using the mapping notation. Compute f(1/2) and f(0.5), and comment on the different results.
You can use a⁢bg to compute the least common multiple of two integers, a and b, where g is the greatest common divisor of a and b. For example, the least common multiple of 4 and 6 is 12. Write a Maple procedure, LCM, which takes as input n>0 integers a1, a2, ... , an and and computes their least common multiple. By convention, the least common multiple of zero and any other number is zero.
Write a Maple procedure called Sigma which, given n>1 data values, x1, x2, ..., xn, computes their standard deviation. The following equation gives the standard deviation of n>1 numbers, where mu is the average of the data values.
sigma=∑i=1nxi−mu2n
Write a Maple procedure which, given a list of lists of numerical data, computes the mean of each column of the data.
Write a Maple procedure called Position which returns the position i of an element x in a list L. That is, Position(x,L) should return an integer i>0 such that L[i]=x. If x is not in list L, 0 is returned.
Download Help Document