411 REFERENCE · Wikibooks
Maxima/Programming
Reference material by Wikibooks contributors. This retains its source identity and is not 411 news reporting.
Maxima Programming
Although Maxima is not a general-purpose programming language, Maxima as a CAS includes among the built-in ingredients a relatively complete set of programming expressions that are useful for the intended context. Many of the code examples of this section are intended to be illustrative of technical details relevant to Maxima programming as their purpose, and consequently are intentionally elementary in character.
Learning any sort of programming language, including Maxima programming expressions, is a subjective process. In any case, it is essential to have enough technical know-how specific to the programming language in order to begin the learning process, even in cases where much positive transfer from other programming experience is involved.
Much can be accomplished mathematically using Maxima without having to resort to in-depth knowledge and proficiency in the technical details of Maxima programming expressions. However, Maxima programming expressions are frequently essential for the purpose of defining functions, both in the programming sense of the term, but also more relevantly, for the mathematical sense. For that reason alone, familiarity and sufficient proficiency with Maxima programming expressions should be given some attention.
One way to begin the learning process is to become familiar with enough of the programming essentials to accomplish independent efforts toward accomplishing some specific programming goal of interest. The other way is to study the many available programming expressions of others. This section is intended to provide the essentials toward learning by whatever means are chosen.
The following table shows some built-in ingredients related to Maxima programming expressions:
+Maxima Programming Categoryifunlesserrcatchprederrorblockforerrorreturncatchfromwarningthrow localthruerrormsgsstatusgarbage_collectsteperrormsgstatusdonextgowhilein
Some of these programming expression ingredients can also be considered as belonging to one or more other categories than the Programming category.
Block Expressions
Arguably, the first expressions to consider in the context of Maxima programming are block expressions. All block expressions as programming ingredients come in two flavors:
( expr1, expr2,…, exprn )
block ([v1,v2,…,vm], expr1, expr2,…, exprn)
The first form of a block expression is equivalent to the second form, but without the first list argument: [v1,v2,…,vm], as in block (expr1, expr2,…, exprn, unless return or go operators are used in the block expression. In that, case the form block (expr1, expr2,…, exprn) must be used. It should be noted that for Maxima to interpret of the syntax of the first of these forms as a block operator, there must be at least two expressions in the comma-separated sequence of expressions as arguments. Otherwise, Maxima will interpret the parentheses of this syntax as simply grouping related to the precedence order of operations.
The purpose of the list argument, [v1,v2,…,vm] is to declare the values of identifiers of the list to be local to the block. If there is no need for local identifiers, the list argument can be omitted (unless return or go operators are used in the block expression), and either form is sufficient./* This block expression */
(%i1) (x : 2, x^2);
(%o1) 4
/* Is equivalent to this block expression */
(%i2) block (x : 2, x^2);
(%o2) 4So, in the case where values of identifiers local to the block of expressions are not required, then the choice of either form of the block expression is a matter of user preference. In both cases, the values of identifiers referenced in the block are global, and not local to the block expression. However, for the sake of clarity and consistency use of the latter form may be a better choice. /* The value 3 assigned to x is global */
(%i3) x : 3;
(x) 3
/* The value 2 assigned to x in the block is global */
(%i4) (x : 2, x^2);
(%o4) 4
/* The global value assigned to x in the block is 2 not 3 */
(%i5) x;
(%o5) 2Values of identifiers local to the block may not be global:/* the value of identifier y is local only */
(%i6) block ([y], y : 4, y);
(%o6) 4
/* the global value of identifier y is not 4, but is itself */
(%i7) y;
(%o7) y
/* the values of identifiers x and y are both local */
(%i8) block ([x,y], x : 3, y : 4, x);
(%o8) 3
/* the global value of identifier x is not 3, but is 2 */
(%i9) x;
(%o9) 2
/* the global value of identifier y is not 4, but is itself */
(%i10) y;
(%o10) y
Regardless of the form of the block expressions these have in common the comma-separated expression arguments expr1, expr2,…, exprn which are evaluated each in turn from first to last (unless the evaluation sequence is modified by by the go, throw, or return operators), and the value of the block expression is the value of exprn.
The main purpose of the block expression, regardless of the form, is to group expressions that are related in some manner, and the value of the last expression may be the only value of interest. However, there are very few limitations imposed on the nature of the block expression arguments, so it might be the case that the purpose of evaluating the block expression has been achieved before the last expression argument is evaluated, in which case the value of the block expression may not necessarily be the value of the last argument expression.
One way to accomplish that is to use an empty string as the last argument expression with the option flag stringdisp set to false which is the default value.(%i11) stringdisp;
(%o11) false
(%i12) block ([x,y], x : 3, y : 4, x, "");
(%o12)
(%i13) block ([x,y], x : 3, y : 4, x)$
The display of the value of the block expression may be suppressed using a $ to end the input expression instead of a semi-colon. Either technique produces a block expression value, but the value is not displayed. If a return operator is evaluated as a argument expression, this will cause evaluation of expression arguments to end explicitly, and the value of the return operator becomes the value of the block expression.(%i14) is (equal (x, 2));
(%o14) true
%i15) block (if is (equal (x, 2)) then return (x), " x not 2");
(%o15) 2
(%i16) x : 3;
(x) 3
(%i17) block (if is (equal (x, 2)) then return (x), "x not 2");
(%o17) x not 2When a return operator is evaluated within a block expression the result is to stop evaluation of expressions within the enclosing block expression and to return the value of its argument as the value of the enclosing block expression. It therefore can be compared with the return statement found in other programming languages, but with one difference: In Maxima, the return operator affects only the block expression in which the return operator is an expression, and has no effect otherwise.(%i18) test_nested_block (val) :=
block (
[outer_var : 10],
print("Entering outer block expression"),
/* inner block expression */
block(
[inner_var : 20],
print (" Entering inner block expression"),
if val > 5 then
/* another inner block expression */
block (
print (" val > 5"),
print (" Returning only to inner block expression"),
return (inner_var + val)
/* returns only to the inner block expression */
),
/* end of another inner block expression */
print (" inner_var + val is ", inner_var + val)
),
/* end of inner block expression */
print ("Back in outer block expression"),
outer_var + val
/* end of outer block expression */
)$
(%i19) test_nested_block (1);
Entering outer block expression
Entering inner block expression
inner_var + val is 21
Back in outer block expression
(%o19) 11
(%i20) test_nested_block (6);
Entering outer block expression
Entering inner block expression
val > 5
Returning only to inner block expression
Back in outer block expression
(%o20) 16
The block operators: Each instance may define local variables and has a comma-separated sequence of expressions as arguments.
Nested blocks: There are three block operators used in the right-hand side of the function definition expression named test_nested_block (val. There is an outer block expression that has an inner block expression as an argument,and there is a third block expression that is an argument of the inner block expression.
The return operator: If val > 5 is true then the return operator of the most inner block expression is evaluated and evaluation of remaining expressions of that block expression does not occur. The value of that most inner block expression is the value of the argument of the return operator – inner_var + val.
Evaluation of expressions continues with the first expression of the inner block expression after the end of the most inner block expression – which is print (" inner_var + val is ", inner_var + val). The value of the print operator is the value of inner_var + val. However, while that value is the value of the inner block expression it is effectively lost, as it is not the last expression of the outer block sequence of expressions. That value is the value of outer_var + val, which, being the last expression of the outer block is always the value of the function.
Block Expression Evaluation Sequence Modifications
block expression arguments expr1, expr2,…, exprn are generally evaluated each in turn from first to last, and the value of the block expression is the value of exprn unless this sequence is modified by some means.
One way to modify the first-to-last expression evaluation sequence of a block expression is by way of a go operator with a label (or "tag") argument ( the "go to") as the following exemplifies:(%i18) sum_to_n (n) := block ( [i : 1, total : 0],
loop,
total : total + i,
i : i + 1,
if i <= n then go (loop),
total)$
(%i19) sum_to_n(25);
(%o19) 325
(%i20) n;
(%o20) n
(%i21) i;
(%o21) i
(%i22) total;
(%o22) total
Note the following:
block ([ i : 1,total : 0], ...): The list argument creates a local environment with local identifiers i and total. The function argument n is also a local identifier.
loop: This label serves as a target inside the block expression to where go (loop) can jump.
if i <= n then go (loop): This conditional expression causes the go (loop) operator to be evaluated if the condition is true.
total: The value of this local identifier is the returned value of the block expression.
It should also be worth noting that the identifiers n, i, and total have local values assigned within the block expression, but their global values are themselves. Also notice that the identifiers i and total were both declared local and assigned initial values within the block list argument.
The go-to label argument of the go operator must be the name of a tag appearing within the same block expression. The go operator cannot be used to go to a tag in a different block expression. There is nothing special about the loop identifier name used for the go-to tag in this example. Other identifiers could be used, but they must be atomic.
Another way to modify a block expression evaluation sequence is by way of the throw operator.(%i32) /* Define a function using a block expression that searches
for a specific condition and a throw value to catch */
find_element (target_list, threshold) := block (
[found_val : false], /* local identifier value */
catch(
for x in target_list do
(
if x > threshold then
(
/* Immediately exit the block and
return this value */
throw (x)
)
),
/* This string is returned only if the do loop
finishes without a throw to catch */
"No element exceeded the threshold"
)
)$
(%i31) /* Test the block */
find_element ( [10, 20, 55, 80], 50 );
(%o31) 55This example unpacks as follows:
catch (...): This expression creates a target scope that "catches" the value of any throw expression evaluated within that scope.
throw (x): When Maxima encounters this operator, it immediately stops evaluation of the do loop expression and the rest of the block expression. It jumps directly out of the scope of the catch expression, returning the value of x as the value of the entire catch expression.
Nested Block Expressions
block operators may appear within other block expressions as arguments – nested block expressions. A local environment with local identifiers may be created for nested block expressions. Local values for identifiers, if declared by a first block operator list argument, are created each time a nested block is evaluated.
However, identifiers with global values that have not been declared as local within any block expressions, are global values for nested block expressions as well as enclosing block expressions. Likewise, local identifiers of any enclosing block expression have values that are global within any enclosed blocks, if not declared as local within the enclosed block. If the value of an identifier is not local in a block expression, its value is the value most recently assigned by an enclosing block expression, if any value has been assigned. Otherwise, it is the value of the identifier in the global environment. (%i23) z : 2^(1/3);
(z) 2^(1/3)
(%i24) /* Outer block starts */
block
(
[x : 10, y : 20],
/* values of x and y declared local to outer block */
/* Inner block starts */
block
(
[y : 99, z : 100],
/* values of y and z declared local to inner block */
print ("Inside inner block:"),
print ("x =", x, "is the value of x from outer block"),
print ("y =", y, "is the local value of y of outer block"),
print("z =", z, "is the local value of z of inner block")
),
/* Inner block ends */
/* Outer block resumes */
print ("Back in outer block:"),
print ("x =", x, "is the value of x from outer block"),
print ("y =", y, "is the restored value of y of the outer block"),
print ("z =", z, "is the value of z global to both blocks")
/* Outer block ends */
)$
Inside inner block:
x = 10 is the value of x from outer block
y = 99 is the local value of y of outer block
z = 100 is the local value of z of inner block
Back in outer block:
x = 10 is the value of x from outer block
y = 20 is the restored value of y of the outer block
z = 2^(1/3) is the value of z global to both blocks
Values, Properties, and the local Operator
The expression local (v1, v2, ... , vm) within a block expression saves the properties associated with the symbols v1, v2, ... , vm, removes any properties before evaluating other expressions, and restores any saved properties on exit from the block.
Because Maxima is an application based on the Common Lisp programming language, it utilizes Lisp symbol structure. Lisp symbols are objects that carry their own "dictionary" of attributes. A Lisp symbol's internal property list (plist) is a built-in data structure attached directly to a symbol. Maxima uses this underlying Lisp symbol structure to associate properties with Maxima identifiers.
Properties of identifiers can be declared explicitly by users. (%i23) declare (x, real);
(%o23) done
However, some identifier property declarations are implemented implicitly as properties of a symbol, including :=, array, dependencies, atvalue, matchdeclare, atomgrad, constant, nonscalar, assume, and some others. Below is a description of how each of those specific declarations is implemented as a property under the hood:
Functions and Arrays
:= (Function Definition): When you define a function using f(x) := x^2, the actual expression (x^2) and its argument list (x) are stored as a property of the symbol f.
array: When you declare or use a subscripted array (like a[i]), the array type, dimensions, and memory pointer are attached as a property to the symbol a. This differentiates it from a standard scalar variable.
Calculus and Dependencies
dependencies: If you declare depends(y, x), Maxima attaches a dependency list property to y noting that it functionally depends on x. This tells the diff (differentiation) engine not to treat y as a constant relative to x.
atvalue: When you define a specific value for an expression at a boundary point (e.g., atvalue(y, x=0, 1)), this boundary constraint is stored as an evaluation property on the symbol y.
atomgrad: This assigns a specific gradient or derivative to an atomic identifier (e.g., telling Maxima what (df/dx) is explicitly). Maxima saves this rule as a gradient property on that symbol.
Pattern Matching and Rules
matchdeclare: Used in Maxima's pattern-matching system (like tellsimp). When you type matchdeclare(x, freeof(a)), Maxima attaches a predicate property to x. When a pattern rule runs, Maxima looks at property list of x to see what conditions a matching expression must meet.
Mathematical and Evaluation Attributes
constant: Declaring declare(c, constant) places a "constant" flag on property list of c. Core routines (like integration and differentiation) check this flag to know they can pull c out of integrals or differentiate it to zero.
nonscalar: Tells Maxima to treat a symbol as a matrix or vector rather than a regular number. This property changes how operators like . (non-commutative multiplication) treat the symbol.
assume: When an expression such as assume(x > 0) is used, Maxima's database system (context) assigns facts and properties (like pos or nonnegative) to the symbol x, allowing the simplifier to safely reduce expressions like sqrt(x^2) to x.
The effect of the local operator is to make such declarations effective only within a block expression. Otherwise, declarations within a block expression are considered to be global declarations.
So what does all of this mean to the typical Maxima user? It means that the list of identifiers that is the first argument of a block expression only declares the values of those identifiers as local to the block expression in which the list is an argument. Any properties of identifiers are not declared local by this means.
In order to declare properties of identifiers to be local to a block expression, the local operator must be used as an expression within the block expression. At the point where the local operator is evaluated as an argument of a block expression, the properties of the identifiers that are arguments to the local operator become local to the block. It is not an unfair criticism that perhaps the local operator should have been named something like local_properties. (%i25) f (s) := cos (s);
(%o25) f(s):=cos(s)
(%i26) f (%pi/6);
(%o26) sqrt(3)/2
(%i27) block (local (f), f (s) := sin (s), f (%pi/6));
(%o27) 1/2
(%i28) f (s);
(%o28) cos(s)In the above example, the properties of identifier f were declared to be local in the block expression using the local operator. However, global properties of the identifier f were not affected by the local function definition of f in the block expression. The global definition expression of f which is cos (s) and the argument s were not affected by the local definition of f because the properties of f were declared local to the block expression.
block expressions can be used for different purposes in the Maxima programming context. block expressions can be used in a stand-alone fashion or as part of an expression. However, block expressions are commonly used in expressions that define functions, which is the topic that follows.
Functions
+Function Definition Operators
and Identifiers:=fundefbuildq::=lambdamacrosdefinelocalslicedispfunremfunctionfunctionssetup_autoload
As a technical term function is aptly characterized as being overloaded. In the context of Maxima as a CAS, it has both a programming meaning and a mathematical meaning in terms of usage. In this book, the convention is to refer to what may otherwise be referred to as built-in Maxima functions as operators including those of built-in packages. By this convention, the term function in this book refers exclusively to expressions composed by the user by way of the := or define operators.
As has been mentioned elsewhere in this book, what are conventionally termed operators that are built-in to Maxima, are at the level of underlying expression construction, Lisp procedures of one type or another. So it makes sense to simply refer to built-in Maxima functions and operators simply as operators one and all. This convention obviates the need for the repetitious use of qualifiers such as built-in Maxima and user-defined functions.
There are two operators used to define functions as follows:
The := operator
The define operator
f (x1, x2, ..., xn) := expr
f [x1, x2, ..., xn] := expr
f [x1, x2, ..., xn](y1, y2, ..., ym) := expr
define (f (x1, x2, …, xn), expr)
define (f [x1, x2, …, xn], expr)
define (f [x1, x2, …, xn](y1, y2, …, ym), expr)
define (funmake (f, [x1, x2, …, xn]), expr)
define (arraymake (f, [x1, x2, …, xn]), expr)
define (ev (expr1, expr2))
where expr is some Maxima expression that is commonly referred to as the function body. As can be concluded from the above, each of these operators has different forms of syntax that can be used. In any case, the most significant differences between these two operators are in terms of the evaluation of the function body and the composition and identification of functions dynamically.
Evaluation:
The := operator suppresses (quotes) the evaluation of the function body when this operator is first evaluated. It does not evaluate or simplify the function body until the function identifier itself is evaluated with arguments.
The define() operator evaluates the function body when this operator is first evaluated. If the function body involves intermediate computations, substituted values, or extracted parts of expressions (like rhs() or derivative results), the define() operator includes the evaluated results of those directly into the function body.
Dynamic Function Composition and Identifiers:
Function identifiers cannot easily be composed dynamically on the left-hand side of the := operator when used inside programmatic loop expressions or in scripts.
The define() operator accepts expressions like funmake or evaluated symbols as its first argument, allowing the composition and identification of functions dynamically during evaluation.
So, there are in general two different operators that can be used to define and evaluate functions. The choice by the Maxima user between these two operators depends on the purpose and context.
Function Definition Using the := Operator
For the purpose of understanding the two ways of defining functions, consider the following example which is elementary, but in terms of clarification is not merely simple:/* "kill" all previously used identifier and values */
(%i29) kill (all);
(%o0) done
/* assign identifier x the global value of 5 */
(%i1) x : 5;
(x) 5
/* define f as a function with local argument x */
(%i2) f (x) := x;
(%o2) f(x):=x
/* evaluate f at local value of x is 3 */
(%i3) f (3);
(%o3) 3
/* the global value of x is not affected */
(%i4) x;
(%o4) 5
/* show the definition expression of the function named f */
(%i5) fundef (f);
(%o5) f(x):=xFunction Definition Using the define Operator/* define a function with identifier g with a
local argument y and with a function body
of identifier x */
(%i3) define (g('x), x);
(%o3) g(x):=5
/* identifier x of the function body is evaluated */
/* identifier x as an argument of g is local in scope */
(%i4) fundef (g);
(%o4) g(x):=5
/* the same identifier x used in three different roles */
/* used as the global name of a function x */
/* used as a local argument of a function */
/* used as an unevaluated expression for a function body */
(%i7) x(x) := x;
(%o7) x(x):=x
(%i9) x(3);
(%o9) 3
(%i10) x;
(%o10) 5
(%i8) fundef (x);
(%o8) x(x):=x
(%i11) define (x('x), x);
(%o11) x(x):=5
(%i12) fundef (x);
(%o12) x(x):=5The purpose of this example is not to encourage this sort of syntax as a typical choice for identifiers and function definitions. The purpose is to clarify how Maxima implicitly determines the scope and meaning of identifiers used for function definition in the absence of any explicit user stipulations.
It should be noted that the define operator not only evaluates the expression that is the function body, it also evaluates the arguments of the function identifier that is the first argument of the define operator. Since there is a global value of the identifier x of 5, it was necessary to suppress evaluation (quote) the argument in the expression (%i12) define ('x), x); as otherwise an error would be the result:/* define a new function named x */
(%i14) define (x('x), 'x^2);
(%o14) x(x):=x^2
/* check the definition of the new function named x */
(%i15) fundef (x);
(%o15) x(x):=x^2
/* the identifier x has a global value */
(%i16) values;
(%o16) [x]
/* the global value of identifier x is 5 */
(%i17) x;
(%o17) 5
/* the suppressed global value 'x of identifier x is x */
(%i18) 'x;
(%o18) x
/* not quoting the global value of identifier x is an error */
(%i19) define (x(x), 'x^2);
define: in definition of x, parameter must be a symbol and must not be a system constant; found: 5
-- an error. To debug this try: debugmode(true);
Understanding the Differences Between the := and define Operators
In cases such as these, the suppression of evaluation by quoting is a user stipulation as to evaluation. Users must be aware of these differences between the two operators (:= and define) for the purpose of function definition in order to achieve what is intended and to avoid errors,(%i20) f (x) := 3*x^3 + 4*x - 5;
(%o20) f(x):=3*x^3+4*x-5
(%i21) fundef (f);
(%o21) f(x):=3*x^3+4*x-5
(%i22) f(2);
(%o22) 27
(%i23) define (f (x) , 3*x^3 + 4*x - 5);
define: in definition of f, parameter must be a symbol and must not be a system constant; found: 5
-- an error. To debug this try: debugmode(true);
(%i24) define (f ('x) , 3*x^3 + 4*x - 5);
(%o24) f(x):=390
(%i25) define (f ('x) , '(3*x^3 + 4*x - 5));
(%o25) f(x):=3*x^3+4*x-5
(%i26) fundef (f);
(%o26) f(x):=3*x^3+4*x-5
(%i27) f(2);
(%o27) 27What is true in the case of these elementary examples is true in general for function definitions involving multiple arguments:/* x has a global value of 5 while y is local to g */
(%i28) g (x, y) := x^3 - y^2;
(%o28) g(x,y):=x^3-y^2
(%i29) g (6,2);
(%o29) 212
(%i30) define (g('x, y), 'x^3 - y^2);
(%o30) g(x,y):=x^3-y^2
(%i31) values;
(%o31) [x]
(%i32) g (6,2);
(%o32) 212The define operator is a good choice in cases where an existing expression is used as the function body:(%i33) /* assign an expression to identifier expr */
expr : diff('x^3, 'x); /* Returns 3*x^2 */
(expr) 3*x^2
(%i34) /* evaluate expr and define h (x) := 3*x^2 */
define (h ('x), expr);
(%o34) h(x):=3*x^2In cases in which functions require local variables or more than one expression for the function body, use the block operator:(%i35) remvalue (x);
(%o35) [x]
(%i36) values;
(%o36) [expr]
--> power_sum (x, y) := block (
/* values of these identifiers are explicitly local to the function */
[sqx, sqy],
/* unevaluated identifiers x and y are local arguments of the function
and values of these identifiers are implicitly local to the function */
sqx : x^2,
sqy : y^2,
sqx + sqy /* the value of the function */
)$
(%i37) fundef (power_sum);
(%o37) power_sum(x,y):=block([sqx,sqy],sqx:x^2,sqy:y^2,sqx+sqy)
(%i38) power_sum (3,4);
(%o38) 25The define operator could also be used to define this function, but with different definition values as a consequence:(%i39) define (power_sum (x,y),
block (
[sqx,sqy],
sqx : x^2,
sqy : y^2,
sqx + sqy
)
)$
(%i40) fundef (power_sum);
(%o40) power_sum(x,y):=y^2+x^2
(%i41) power_sum (3,4);
(%o41) 25The function definition of power_sum is different using the define operator instead of the := operator. The value of the function when referenced with arguments (power_sum (3,4)) is the same in both cases – 25. However, in general for defining functions, this may not be the case. The choice between these two operators depends on the intentions of the user.
Practical Considerations Related to Function Composition
In general, programming is a technical skill as well as a subjective art. So, absolute rules are not offered here. For practical purposes some "guidelines" are offered here that may be useful for Maxima function composition as well as Maxima programming in general:
Consider the following linear algebraic equation:
As an example suppose the value that is sought is the slope m of this equation, and in general, the slope of any linear equation regardless of the form. /* assign the linear equation to an identifier */
(%i42) lineqn : 3*y - 7*x = 10;
(lineqn) 3*y-7*x=10
/* solve the linear equation for y */
(%i43) solve([lineqn], [y])
(%o43) [y=(7*x+10)/3]
/* use the index [1] of the list to get the solved equation */
(%i44) solve([lineqn], [y])[1]
(%o44) y=(7*x+10)/3
/* use the rhs operator to get right-hand side */
(%i45) rhs(solve([lineqn], [y])[1]);
(%o45) (7*x+10)/3
/* use the expand operator change the RHS */
(%i46) expand(rhs(solve([lineqn], [y])[1]));
(%o46) (7*x)/3+10/3
/* use the coeff operator to get the coefficient of x */
(%i47) coeff(expand(rhs(solve([lineqn], [y])[1])),x,1);
(%o47) 7/3
/* define the function with an argument eqn for lineqn */
(%i48) m (eqn) := coeff (expand (rhs (solve ([eqn], [y])[1])),x,1)$
/* check the function definition */
(%i49) fundef (m);
(%o49) m(eqn):=coeff(expand(rhs((solve([eqn],[y]))[1])),x,1)
/* reference the function using lineqn as an argument */
(%i50) m (lineqn);
(%o50) 7/3
/* assign a different linear equation to lineqn */
(%i51) lineqn : x = 6*y + 13;
(lineqn) x=6*y+13
/* reference the function using lineqn as an argument */
%i52) m (lineqn);
(%o52) 1/6The function body of this example will result in an error if it is used with the define operator:(%i53) define (
m (eqn),
coeff (expand (rhs (solve ([eqn], [y])[1])),x,1)
)$
inpart: invalid index 1 of list or matrix.
-- an error. To debug this try: debugmode(true);This error is a consequence of the evaluation that occurs when using the define operator. In this case, this evaluation results in a reference to the indexed value of a list object that does not exist when evaluation occurs during definition – solve ([eqn], [y])[1].
Memoizing Functions
The functions discussed so far are often referred to as ordinary functions. The arguments of ordinary functions are enclosed using parentheses – (x1, x2, ..., xn). Function definition forms which include arguments enclosed by brackets (as in [x1, x2, ..., xn]) are called memoizing functions.
Memoizing functions can be categorized both as functions and as data structures – specifically a hashed array of values. Memoizing functions are also referred to as array functions. In any case, memoizing functions can be defined using either the := operator or the define operator.
If the form of the function definition expression is f [x1, x2,..., xn] := expr The function body expr is evaluated once for each individual value of the arguments, and that value is the value of the function, without again evaluating the function body, whenever referenced using arguments that have those same values.
Once evaluated during a function reference with arguments, that value of a memoizing function is thereby "frozen" in other words. This frozen value also becomes an instance of a hashed array data structure that can be referenced by way of the value of the index used as an argument when first evaluated.
In keeping with previous practice, the examples provided here are intentionally elementary for the purpose of demonstrating and clarifying the technical aspects involved. There is thereby no intention to convey that the composition of function definition expressions with multiple arguments and complex expressions for the function body is ruled out or of lesser practical value. /* define a memoizing function f */
/* x and n may serve as both arguments and array indices */
(%i54) f [x, n] := x^n;
(%o54) f[x,n]:=x^n
/* check the array information for f */
(%i55) arrayinfo (f);
(%o55) [hashed,2]
/* f does not appear in the list of functions */
(%i56) functions;
(%o56) [m(lineqn)]
/* reference f with argument values */
(%i57) f [2,3] ;
(%o57) 8
/* check the array information for f again */
(%i58) arrayinfo (f);
(%o58) [hashed,2,[2,3]]
/* list the array values of f */
(%i59) listarray (f);
(%o59) [8]
/* f does appear in the list of arrays */
(%i60) arrays;
(%o60) [f]
/* reference f with new argument values */
(%i61) f [5,2];
(%o61) 25
/* check the array information for f again */
(%i62) arrayinfo (f);
(%o62) [hashed,2,[2,3],[5,2]]
/* list the array values of f */
(%i63) listarray (f);
(%o63) [8,25]An expression of the form define (f [x1, x2,…, xn], expr) may also be used to define the same memoizing function f in this case as the evaluation imposed by the define operator during evaluation of these function arguments and this function body that occurs makes no difference:/* remove the function definition and values of f */
(%i64) remarray (f);
(%o64) [f]
/* the list of arrays is now empty */
(%i65) arrays;
(%o65) []
/* define the same memoizing function f using the define operator */
/* evaluation makes no difference to the definition of f in this case */
(%i66) define (f [x,n], x^n);
(%o66) f[x,n]:=x^n
/* reference f with different argument values */
(%i67) f [3, 2];
(%o67) 9
/* reference f again with different argument values */
(%i68) f [2, 5];
(%o68) 32
/* f again appears in the list of arrays */
(%i69) arrays;
(%o69) [f]
/* check the array information for f */
(%i70) arrayinfo (f);
(%o70) [hashed,2,[2,5],[3,2]]
/* list the array values of f */
(%i71) listarray (f);
(%o71) [32,9]
Lambda Expressions
Special Forms of the define Operator
Some Techniques for Function Composition
Conditional Expressions – Special Operator if
Expressions for Iteration – Special Operator do
The special operator do is used to compose expressions for iteration. It is typically used with the following other special operators:
while
unless
for
from
thru
step
next
in
The general forms of do expressions Maxima supports are:
for variable: initial_value step increment thru limit do body
for variable: initial_value step increment while condition do body
for variable: initial_value step increment unless condition do body
for variable in list do body
for variable in expr do body
for variable in hash_table do body
Lisp and Maxima