Search code examples
functionglobal-variableslocal-variablesmaximawxmaxima

Maxima: Force a function to run with local-variables only? / How to Avoid Local Operations Affecting Global-Variables?


I've just discovered that I have some basic misunderstanding about how block works for functions.

In general, I thought "what happens in a function, stays in a function (unless returned);" however, this doesn't seem to be the case, so I was hoping someone could help me get a handle on the situation.

A quick example:

/* Define & Initialize Variables */
[a,b]:[7,-5];

aFunction():= block([c:a, d:b], a:1, b:2,[c,d]);

Now, if we run the function, we get:

result1 : aFunction(); 
>>> [7, -5]

Which makes sense: c and d are assigned the value of a and b within [ ] which gives the globally set value, so the inner assignment of a and b has no effect on the output.

What surprised me was that running the function a 2nd time:

result2 : aFunction();
>>> [1,2]

I can only conclude that the assignments a:1 and b:2 that occur within the block of aFunction() actually affect the global variables. If this is true, how do you structure your work so that this behaviour won't come back to bite you? The only completely safe way I see, right now, is to start a new document every time, which is a bit tedious. Is there another approach to ensuring that the work within a function won't affect what's happening outside the function? I've provided an example of the type of issue I've encountered, below, for more context.


An example of how this bit me was that I defined a variable defaultValue that was not previously declared elsewhere in the document, within the [ ] section of the block:

newFunction():=block( [ defaultValue : 10, a : defaultValue ] random(a) );

The function didn't behave as expected, because of parallel assignment of variables within the [ ] section (because defaultValue wasn't defined globally, it was trying to evaluate random(defaultValue)).

I didn't know about parallel assignment then, so I tried moving the assignment to the main body of the block.

newFunction():=block( [ a : defaultValue ], defaultValue : 10, random(a) );

Of course, the first run didn't work either, so I thought the issue wasn't about the placement of the assignment. I then found something else that I thought must be the bug, and so, after resolving that problem, I tried putting the assignment back into the [ ] section:

newFunction():=block( [ defaultValue : 10, a : defaultValue ] random(a) );

Of course, because I had executed the function, in the meantime, defaultValue had been defined, globally (which I also didn't know).

I only realized that something was awry because, distrustful of the black magic that is Maxima, in general, I wanted to be sure it was working as expected, so tried changing the value of defaultValue, only to see that it had no effect (thank you parallel assignment...).

As mentioned, I believe the reason the code was executing is because, when I moved the assignment of defaultValue to the body, it saved that value to the global context. Aside from knowing about this particular case, it's not obvious to me what best practices could protect against these kinds of issues. Is there a way to limit the scope of a function to only locally defined variables?


Solution

  • There are a couple of things going on; I'll try to sort it out.

    One is the question of distinguishing local and global variables in a block. It turns out the answer is just this: a variable in a block is local if and only if it is named in the list of variables at the beginning of an enclosing block, otherwise it is a global variable. Note that this covers nested blocks. A variable is global if and only if it is not named in the list of variables at the beginning of any enclosing block.

    The other issue is what values are used to initialize. The answer is just that the right-hand side of the assignment is the value of the expression outside of the block. E.g., in block([a: foo(a)], ...) the expression foo(a) is evaluated with the value of a outside the block, and then assigned to a inside the block.

    By the way, ([a, b, c], ...) does not treat a, b, and c as local variables. You must say block([a, b, c], ...) to get local variables a, b, and c.