Expected behaviour of 'For' loop

Community Forums/General Help/Expected behaviour of 'For' loop

col(Posted 2014) [#1]
Hiya,

The following 'for' statement :-

Local i:Int
For i = 0 Until Func(i)
Next

Function Func(a:Int)
	Return a + 1
EndFunction


What's your thoughts on the expected behaviour of using Func(i) as the terminating value?

Should the terminating value be evaluated before the loop starts or keep evaluating during each iteration of the loop?


GfK(Posted 2014) [#2]
I don't understand? Are you saying it shouldn't accept a function call as a parameter?Oh, you edited it.

Um... I'd expect it to only be evaluated once.


col(Posted 2014) [#3]
My mistake.. I posted too early by accidently hitting the tab then return. I've edited it now.


GfK(Posted 2014) [#4]
Hmm... seems to be working as I'd expect it to? You expecting something different to happen?
Local i:Int
For i = 1 Until Func(i)
	Print "Bumeyes"
Next

Function Func(a:Int)
	Return a + 20
EndFunction



col(Posted 2014) [#5]
Not really, I was asking for opinions for what seems/feels right.

I'm writing a compiler and was interested in what a programmer would expect to happen when the iterator of the for loop is used as a parameter in the terminator function value. I can choose either way as the compiler outputs c++ - giving a choice of behaviors.


col(Posted 2014) [#6]
I've actually decided to go the other way - re-evaluate the func each iteration. It gives the best of both making for the option of a little more power and expression in the language. If you only want it evaluated once then it's easy to stick the result in a local before the loop and use the local as the terminator.


GfK(Posted 2014) [#7]
Glad my advice helped! :P


Yasha(Posted 2014) [#8]
If there's still time to offer opinions, I'd agree with GfK here, for two reasons:

1) 'For' usually implies 'For each in'; i.e. that it is for iterating over a collection of some kind (where the integer 'to' form is just a special case or optimisation of the primary list form that recognises a range+step as an abstract list). So if you put a function call in that position to something iterating over an object collection, you wouldn't expect a whole new collection to be returned each time. (If you do want something new to happen in the call each frame to get the next element, that's usually why we have things like ObjectEnumerator, or generators in other languages.)

2) We already have a standard construct dedicated to having a function get called again each time to see if something changed - 'While'. May as well differentiate them as much as you can.

...and add that C's 'for' is a bit of a special case - it's not really a high-level construct at all, without the same intent behind it as the similarly-named thing in other languages, so beware of potentially being misled by it.


EDIT: actually I can think of a third, too, which I believe is the same reasoning Mark gave for changing the behaviour between BlitzPlus and BlitzMax: optimise for the common case. Most loops will be integer loops over fixed bounds; shorter, clearer and therefore objectively better code will not waste a local on a single space, and thus put the function call in the 'To' position; so if you default to rerunning the target bound every frame, you're punishing the user for writing good code. A good compiler absolutely must never demand that the user hand-optimise their code for better performance - it's supposed to be trying as hard as it can to do that automatically. If you have to put in the docs "write like this for better results", that should technically count as a compiler bug.

(I should also point out that this situation actually links back really well to your previous question - a purity-analysis and flow-analysis pass can make this question irrelevant in 99% of realistic code if you do go with the second behaviour.)


col(Posted 2014) [#9]
Hmm...

2 against 1 at the moment :)
To be honest I dont mind either. And I see yours and GfKs points.

The way I have it at the moment it as Max is :-

for <ident of int/float> = <expression of int/float> To/Until <expression of int/float> step <expression of int/float>

The 'step' is optional as one may expect.
As you can see the expressions must equate to int/float for the statement to be valid. Of course, a function return result can be used as, or part of, an expression so the return result must still be an int/float. There are no objects/arrays/collections allowed to be used in the statement at all ( apart from object members/functions that still follow the above rule ).

So I was just in 2 minds whether to allow the function be called each iteration or just the once during the initial first iteration. I was thinking this could be more of feature and most definitely not a requirement. For regular integers/floats in the statement it will behave exactly how you expect.

Now thinking about it some more - I'm in 2 minds again :D

What is difference between Max and Plus in this respect? I have 'Plus but I don't think I've ever used it.


Yasha(Posted 2014) [#10]
What is difference between Max and Plus in this respect?


BlitzMax evaluates the expression once before the loop begins. BlitzPlus - and the linguistically-identical compilers that came before it - evaluates it repeatedly before each run through the loop body. So you have both templates there to look at.

BlitzMax is the one that generalised For to mean collections, and provided ObjectEnumerator; in the Classic language, For/Each iterates over a builtin global list for each user-defined type, so there is no expression for it to repeat a call to; For/To and For/Each are completely conceptually unrelated over there.


Note also that your original example would normally be either iffy or even wrong in BlitzMax proper (because Max introduces a limited form of scope nesting): since the controlling expression happens before the loop, Func(i) is outside the scope of the actual definition of i; if you'd used For Local this would (probably*) be a variable Not Defined error, or connect to the wrong variable; and in non-Strict would probably be undefined. In Classic this is a nonissue because scopes are not nested.

* can't be bothered to check but if it's not an error, it's an inconsistency in the language definition.


col(Posted 2014) [#11]

Note also that your original example would normally be either iffy or even wrong in BlitzMax proper (because Max introduces a limited form of scope nesting): since the controlling expression happens before the loop, Func(i) is outside the scope of the actual definition of i; if you'd used For Local this would (probably, won't check) be a variable Not Defined error, or connect to the wrong variable; and in non-Strict would probably be undefined. In Classic this is a nonissue because scopes are not nested.



'Max does error. I have this same behaviour.
I think I'll leave it as it is for time being.


Derron(Posted 2014) [#12]
Shouldn't for-loops be "pre-evaluated" (constant) while a WHILE-WEND/REPEAT-UNTIL loop evaluates each loop?


bye
Ron


col(Posted 2014) [#13]
Well, obviously not 'constant' by definition but I know what you mean and it seems thats the general expectation so far :)

I think it depends on the implementation and the language designer as to whether to have re-evaluation on each iteration. I just quickly checked out how Monkey handles it and its the same as previous Blitz*name* languages - it re-evaluates a function call expression each iteration.


Derron(Posted 2014) [#14]
Re-Evaluation is complicating some situations (adding need for previous variable declaration)

Surely there is advantage in "for eachin EnumeratorFunction()" but most of the times I use the for loop is when the "eachin X" corresponds to something non-changing-during-loop (changes to that object should be added "at the end").

If I do not know if that X changes I use a While-Wend with a "counter", so I do it as long as a Getter(counter) returns something.


Just have seen how Monkey (or Diddy) does Lists... seems more complicated to me than needed. Sure...it adds more potential but therfor - like said - I would use RepeatUntil or WhileWend. As they by definition are loops with condition checking - so coders would assume that the condition is rechecked every loop.

bye
Ron


col(Posted 2014) [#15]
Hmm.

Both Repeat and While require an 'external' Local too. Is that really a valid case for not using For?

However I understand in that you would expect a For loop to iterate using 'static' variables. As you've all said - you're expecting to iterate over an existing collection, not a 'possibly changing' collection.

The favour now seems against re-evaluation :P


Derron(Posted 2014) [#16]
That is just _my_ favour. If it is _your_ language, you define the cases.

All "non-reevaluated" loop-conditions use an external-local (hidden from the coder).


But you go d'accord with me about WHILE? I am German so my understanding of the English language may be not 100% perfect.

My understanding of the "meaning" of a phrase is the following:
FOR all in this box DO eatThem()
-> you can throw the content of the box in another box - and if you ate everything in that other box, you finished your task, you ate all which WAS in the box

WHILE something is in this box DO eatThem()
-> as soon as you find something in this box you will have to eat it to fulfill the task. If one replenishes it, you have to eat again

(of course this ignores the head/foot-condition checking as REPEAT eat UNTIL .. won't work with that example)

So if you think about it in the same way - you should transfer this expectation to your language. If you think: there is no difference between a "for everything on this list" and a "while there is something on this list" then you do it by throwing a dice or look how other bigger languages do it.
Especially if you enable "importing externals" you could try to have all importable languages behave the same way.


Hmm, seems my post just made myself uncertain about "for and while" in English. Maybe because we translate "while" as "So lange" ("as long ...") which might change meaning a bit.

bye
Ron