When calling a procedure with some arguments, you expect the procedure to assign these values for its local variables and perform some computations with those variables. For example, this procedure divides any number that you pass to it by 10:
f := x -> (x := x/10):
In this example,
x is a local variable of
When you call
f with any value, the procedure assigns
that value to the local variable
x, uses it to
compute the result, and then destroys the local variable
x := 10: f(x), x
Although the value of the local variable
1 inside the procedure and then gets destroyed,
the value of the global variable
x remains the
same. Therefore, you can conclude that the procedure does not access
the actual memory block that contains the value of
When you call this procedure, MuPAD® allocates a new memory
block and copies the value of
x to that block.
While the procedure executes, the system associates the local variable
this new memory block. At the end of the procedure call, it frees
this memory block. The memory block that contains the value of the
x does not change.
The strategy of copying values of procedure arguments to new memory blocks and referring to these blocks during the procedure calls is known as calling by value. Most MuPAD functions use this strategy.
Since calling by value creates extra copies of data, it can be inefficient in terms of memory management. Creating extra copies of large data sets, especially when your procedure calls are recursive or nested can significantly reduce free memory on your computer. In many programming languages, calling by value always means copying data, even when a procedure does not change that data.
MuPAD uses lazy copying to avoid creating unnecessary copies of data. When you call a procedure, MuPAD does not allocate new memory blocks right away. Instead, it links local variables to memory blocks where the arguments of the procedure are stored. The system always counts how many objects are linked to the same memory block. When a procedure modifies the value of a local variable, MuPAD checks if there are other objects linked to the same memory block, and creates a copy only if necessary.
For example, when you call
f(x), MuPAD points
both global variable
DOM_IDENT) and local (
x to the same memory block. Only when
the local variable
x changes its value, MuPAD allocates
a new memory block for it.
Typically, when you call a MuPAD procedure with some arguments, the system uses the calling-by-value approach and creates copies of the values of these arguments. This approach prevents procedures from modifying objects passed to a procedure as arguments. For some functions, such as assignment, MuPAD uses the calling-by-reference approach. In a call by reference, the system does not copy the values of arguments to new memory blocks. Instead, it copies references (pointers) to these arguments.
Some programming languages let you specify which approach to use for each particular function. MuPAD does not offer specific language constructs for calling by reference. Nevertheless, you can still call by reference in MuPAD.
Note: For experienced MuPAD users, objects with reference semantics can behave unexpectedly. Be careful when exposing reference semantics to your users because it can be confusing.
Suppose your task requires a function call to be able to change
the values of its arguments. The simple strategy is to return a modified
copy of the arguments and overwrite the original object by using assignment.
For example, replace matrix
A with its upper row
A := linalg::hilbert(3)
A := linalg::gaussElim(A)
When working with large data sets and deep nested calls, this strategy can cause memory problems. Check the profiling report to see if your implementation has such problems. For details, see Profiling Your Code.
You also can achieve the calling-by-reference effect in MuPAD using:
Instead of passing data as arguments of a procedure, you can use a local variable of the outer procedure to store the data. For the inner procedure, this variable is not local. Therefore, the inner procedure can change the value of that variable, and the variable is not destroyed after the inner procedure is executed:
f := proc(x) local n, incr; begin n := 0; incr := () -> (n := n + 1); while x > n do incr(); end_while; end_proc:
This approach does not fit many programming tasks, but it is recommended wherever you can apply it. It is the simplest and most readable approach to get the calling-by-reference effect in MuPAD.
When working with domains, you also can use the approach of having the actual data in a closure. For example, instead of storing the actual data in the objects, you can store functions that access the data:
domain d local get, set; inherits Dom::BaseDomain; new := proc(x) option escape; begin new(dom, () -> x, y -> (x := y)); end; incr := x -> set(x, get(x) + 1); print := x -> get(x); begin get := x -> extop(x, 1)(); set := (x, y) -> extop(x, 2)(y); end_domain: e := d(4)
See Closures for more details.
You can implement the calling-by-reference approach in your code by using domains as tables with reference effects. (Using domains as tables is unrelated to object-oriented programming.) The following example demonstrates this strategy:
domain dd local get, set; inherits Dom::BaseDomain; new := proc(x) local d; begin d := newDomain(genident()); d::number := x; new(dom, d); end; get := (x, entry) -> slot(extop(x, 1), entry); set := (x, entry, value) -> slot(extop(x, 1), entry, value); incr := x -> (dom::set(x, "number", dom::get(x, "number") + 1); x); print := x -> dom::get(x, "number"); end_domain: e := dd(4)
f := plot::Function2d(sin(x), x=-PI..PI): f::Color := RGB::Green:
option hold also let you implement the calling be reference effect
in your procedures.
option hold prevents the procedure
from evaluating its arguments before executing the code in the procedure
lets you perform operations as if they occur in the calling procedure.
For example, in this code
option hold prevents
incr procedure from evaluating its argument.
Therefore, the system does not copy the value of
a new memory block:
incr := proc(x) option hold; begin context(hold(_assign)(x, x + 1)); end_proc: operator("++", incr, Prefix, 500):
While executing this procedure, the system performs the assignment
operation in the context of the procedure
calling procedure for
the value of the argument,
n, with which it was
f := proc(x) local n; begin n := 0; while x > n do ++n; end_while; end_proc:
If you use the
++ operator on an unchangeable
object, MuPAD throws an error. For example, you cannot assign
the value 2 to the value 1:
Error: The left side is invalid. [_assign]
This error message does not mention
the error occurs in the assignment which is performed in a different
evaluation context. The
incr procedure behaves
essentially like a dynamic macro.