2

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?

Rax Adaam
  • 790
  • 3
  • 11

2 Answers2

2

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.

Robert Dodier
  • 16,905
  • 2
  • 31
  • 48
  • Thank you so very much for taking the time to explain things so clearly, Robert. Your explanations are invaluable and have really helped me get an initial handle on Maxima, and I really appreciate it. We're just launching our NPO and are operating on personal lines of credit, at the moment, but we will be looking for a Maxima consultant who can help trouble-shoot programming issues we encounter. If ever this might interest you, please let me know, as it would be a pleasure to work with you. – Rax Adaam Nov 29 '20 at 19:22
  • I'd be glad to say a little bit about that. Can I ask you to send a message to my personal email? My email address is first name dot last name at gmail dot com. – Robert Dodier Dec 01 '20 at 01:43
1

While it wasn't my intention to post a Q&A, after a bit more experimentation, I noticed that one solution would be to 'self-assign' all variables, at the start of a function, within the [ ] section of the block. For example:

bFunction():=block([c:a, d:b],a:1,b:2,[c,d]);
cFunction():=block([A:A, B:B, C:A, D:B],A:1,B:2,[C,D]);

If each of these were run in an environment where none of the variables had been assigned, then whereas subsequent executions of bFunction() would change:

  1. bResult1 : bFunction(); >>> [a,b];
  2. bResult2 : bFunction(); >>> [1,2];

Multiple executions of cFunction() would not:

  1. cResult1 : bFunction(); >>> [A,B];
  2. cResult2 : bFunction(); >>> [A,B];
Rax Adaam
  • 790
  • 3
  • 11