Is the Order of Function Evaluation Guaranteed when Function Outputs are Concatenated into an an Array?

Suppose I call two functions within concatenation into an array
A = [ones(1,2),zeros(1,2)]
A = 1×4
1 1 0 0
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
In this case, the order in which ones and zeros are evaluated doesn't matter as far as the result is concerned.
But what about when there is dependence between the two (or more) functions whose outputs are being concatenated:
A = [figure,plot(1:3)]
A =
1×2 graphics array: Figure Line
Is there any guarantee that the call to figure is evaluated and the figure created before the call to plot so that the plotted result is in the newly created figure?
Does the answer change if creating a cell array?
A= {figure,plot(rand(2))}
A = 1×2 cell array
{1×1 Figure} {2×1 Line}

 Respuesta aceptada

The order is guaranteed to be "as-if" it were left to right,
It appears that values are stored temporarily for processing according to order of operations
A(1)^-A(2)^-A(3)^-1
function called 1 times with parameter 1 function called 2 times with parameter 2 function called 3 times with parameter 3
ans = 1.0086
Notice the function calls are reported in left to right order.
But
2^-3^-4^-1
ans = 1.0086
(2^-3)^-4^-1
ans = 2.4414e-04
2^-(3^-4)^-1
ans = 1.0086
This illustrates that the ^- operator,
"Although most operators work from left to right, the operators (^-), (.^-), (^+), (.^+), (^~), and (.^~) work from second from the right to left. "
if the order of operations was strictly left to right then the output value would be the 2.4414e-04 of (2^-3)^-4^-1, but instead it is in line with 2^-(3^-4)^-1 indicating that indeed chains of ^- proceed from second to the right. But since the order of function calls was not parameter 2 followed by parameter 3 followed by parameter 1, it followed that the calls took place left to right and the intermediate values were buffered.
2^(-3^-4)^-1
ans = 1.0086
2^-3^-(4^-1)
ans = 1.6818
((2^-3)^-4)^-1
ans = 2.4414e-04
function ret = A(in)
global order
if isempty(order); order = 0; end
order = order + 1;
fprintf("function called %d times with parameter %g\n", order, in);
ret = in+1;
end

12 comentarios

That's interesting, but it also means that function evaluation order is not optimized to the operation order, right? In your example, left-to-right calling is not optimal, because it requires buffering.
Also, it makes me wonder if buffering is always done, even when unnecessary (as with addition) and when the buffers released....
I don't understand how this answer about mathematical expressions relates to the question about matrix construction, which I'm not sure is even considered an expression (mathematical or otherwise). Am I missing something?
I don't understand how this answer about mathematical expressions relates to the question about matrix construction
Well, matrix concatenation is just one particular mathematical expression, right? When a set of function calls appears in any expression on the right hand side of any assignment statement, the Matlab parser faces a decision about what order to call them in. Walter's example seems to suggest that the order is always left-to-right in order of appearance, regardless of the operations being done and whether that's the optimal thing to do for that specific expression.
This relates to you, because it might mean the function calls in your example,
A = [figure,plot(1:3)]
will always be done from left-to-right as well, by that same rule.
I'm not sure I agree that matrix concatenation is a mathematical expression, at least not in this context. The symbols under discussion here, [], {}, , , and ; (similar to ,) aren't Operators aren't discussed at Operator Precedence. AFAIK, the doc doesn't really formally define an expression.
What does left-to-right mean in this case?
A = [figure,plot(1:3);plot(1:3),gcf]
A =
2×2 graphics array: Figure Graphics Line Figure
Because A(1,2) is deleted
A(1,2)
ans =
handle to deleted Graphics
it looks like the order of evaluation is not in linear index order, as I had hypothesized above.
I guess one could say that's left-to-right and row-by-row, but I don't think that follows from the rules of mathematical operators.
The function calls are all in lexical order, regardless of the semantics.
A();
[A(1), A(2)
A(3), A(4)]
function called 1 times with parameter 1 function called 2 times with parameter 2
function called 3 times with parameter 3 function called 4 times with parameter 4
ans = 2×2
11 12 13 14
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
A();
[[A(1); A(2)], [A(3); A(4)]]
function called 1 times with parameter 1 function called 2 times with parameter 2 function called 3 times with parameter 3 function called 4 times with parameter 4
ans = 2×2
11 13 12 14
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
A();
[[A(1) A(2)].' [A(3) A(4)].']
function called 1 times with parameter 1 function called 2 times with parameter 2 function called 3 times with parameter 3 function called 4 times with parameter 4
ans = 2×2
11 13 12 14
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
A();
A([A(1), A(2)])
function called 1 times with parameter 1 function called 2 times with parameter 2 function called 3 times with parameter 11 function called 3 times with parameter 12
ans = 1×2
21 22
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
function ret = A(in)
global order
if nargin == 0; order = 0; return; end
if isempty(order); order = 0; end
order = order + 1;
fprintf("%s\n", compose("function called %d times with parameter %g", order, in));
ret = in+10;
end
I don't understand what "lexical" means in this context. Can you explain/illustrate what that means for the first example shown above?
Lexical is what you earlier called "left to right and row by row". The test shows that the parser simply scans left to right across the lines of Mcode and evaluates function calls in the order it finds them, regardless of the operations they participate in.
Walter's findings are, however, hard to reconcile with what I'm seeing in the Task Manager.
First I did this,
clear all; f=@(x) rand(512,512,512); %x is deliberately not used
A=f(1).^f(2).^f(3);
resulting in the memory usage profile below,
Then I implemented the same thing this way,
clear all; f=@(x) rand(512,512,512);
tmp=f(2).^f(3); A=f(1).^tmp; clear tmp
and the memory usage was just about the same.
I don't see why they would be the same however. In A=f(1).^f(2).^f(3) the quantity f(1) is computed first, but used last. It has to be buffered until f(2).^f(3) is computed, which means there must be three 512^3 arrays in memory at one time. In the second method, only two 512^3 arrays are used at a time. How can they be equally memory-conserving?
A = [figure,plot(1:3);plot(1:3),gcf]
results in A(1,2) being deleted, because the order of creation is A(1,1), then A(1,2), then A(2,1) which deletes A(1,2) because hold is not on; then A(2,2) is evaluated after that.
Ok. So lexical ordering of the element positions, which I think is just a byproduct of how the parser operates as stated by @Matt J. And the functions are evaluated sequentially, i.e., no chance of multithreading the computations of multiple elements (I'm still not sure there's any definitive proof of this, though all the evidence seems to point this way).
In A = f(1).^f(2).^f(3), f(1) might be computed first but it is not used last. It has to be used to compute f(1).^f(2), which is subsequently .^'d with f(3). So I think f(3) is computed last.
B = rand(5,5,5);
f=@(x) B; %x is deliberately not used
A1=f(1).^f(2).^f(3);
tmp=f(2).^f(3); A2=f(1).^tmp; clear tmp
isequal(A1,A2)
ans = logical
0
A3 = (f(1).^f(2)).^f(3);
isequal(A1,A3)
ans = logical
1
Does that imply that the first method also only needs two arrays in memory simultaneously?
BTW, this thread and threads linked therefrom may be of interest to anyone interested in the mess between the implementation and the documentation of how expressions involving "power operators" (quotes because that thread indicates that what the doc identifies as a single "operator" might in some cases actually be treated as two operators, IIRC) are evaluated, at least back in 2024a. Don't know if doc or implementation has changed since then, but I doubt it on both.
OK, disregard my last comment. Here's what I was really trying to show. In the version below, the two computations of A are now equivalent, but f(1) is used last in both cases:
f=@(x) rand(512,512,512);
%Single statement
A=f(1).^(f(2).^f(3));
clear A
%Multi-statement
tmp=f(2).^f(3);
A=f(1).^tmp;
clear tmp; clear A
The Task Manager profile shows that the single statement version consumes more memory, because it buffers f(1) unnecessarily. In the multi-statement version, no buffering is required. In other words, the left-to-right rule makes it so that you are obliged to break up expressions, if you want to optimize memory.

Iniciar sesión para comentar.

Más respuestas (1)

This reasoning might not be bulletproof, but I think the order of funtion calls in any expression has to always go left to right due to rules of precedence. In other words, in an expression like this,
ans = funcA()+funcB()+funcC()
we know that the first addition on the right hand side has to be evaluated first, and therefore funcA() and funcB() have to be available for that. There are ways you can satisfy rules of precedence with the funcx() evaluated in an arbitrary order, but only by temporarily storing all three funcx() outputs simultaneously. But I just don't think they would do that. Programmers want to be able to count on the result of funcA()+funcB() being transient, so that they can predict peak RAM consumption. Tempory caching of all function calls would upset that, especially if the outputs of the funcx() happen to be large arrays.
In addition, there is the test below. No matter how many times I run, I cannot generate an occurence of non-consecutive evaluations.
h=subtest;
f=@() issorted(cell2mat({h(),h(),h(),h(),h(),h(),h(),h(),h(),h(),h(),h(),h()}));
N=2e4;
isConsecutive=false(N,1);
for i=1:N
isConsecutive(i)=f();
end
tf=all(isConsecutive) %Check if result was conseuctive in all cases
tf = logical
1
function h=subtest
i=0;
h=@increment;
function j=increment()
i=i+1;
j=i;
end
end

1 comentario

I don't understand how the funcA()+funcB()+funcC() example relates to matrix construction.
I confess to not fully understanding how that code works, but I get what it shows.
If I had to guess, I'd guess that the matrix ( [] or {} )elements are constructed in linear index order. OTOH, maybe a smart interpreter could do some things in parallel (like the [eig,svd] example) and not guarantee anything about order (much like how cellfun et. al. explicitly say to not assume the order in which the outputs are computed). Unfortunately, I can't find anything in the doc on this question.

Iniciar sesión para comentar.

Categorías

Productos

Versión

R2025b

Preguntada:

el 9 de En. de 2026

Editada:

el 10 de En. de 2026

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by