In general, most programming languages have little quirks like this that can cause big headaches, but good programming practice (like pre-evaluating your functions into variables and sticking those variables into the expression) will generally avoid the headaches so you don’t actually need to learn all of the details.
One other place where pre-evaluation is essential, incidentally, is when working with (pseudo-)random numbers. Suppose, for instance, that your language has a function called rnd() which returns a floating point number uniformly chosen from [0,1). And you want to choose between four possibilities. A naive programmer might write something like
if(rnd() < 0.25) then do A;
else if(rnd() < 0.5) then do B;
else if(rnd() < 0.75) then do C;
else do D;
But this won’t work right, because each rnd() will be called separately. You’ll end up with A happening 1/4 of the time, B happening 3/8 of the time, C happening 9/32 of the time, and D happening 3/32 of the time (but since all of them will sometimes happen, it’ll drive you nuts trying to figure out why the probabilities are wrong, if you even notice). Instead, you need to say
r = rnd();
if(r < 0.25) then do A;
else if(r < 0.5) then do B;
else if(r < 0.75) then do C;
else do D;
which guarantees that it’s the same random number you’re looking at each time.
EDIT:
Or rather, it can, as long as it always does that, and it’s in the documentation that it always does that.
Or, to nitpick yet further, you can have a programming language that’s inconsistent about it, or that is consistent but doesn’t document which way it does it, but that programming language is a bad one.