FOR bug

BlitzMax Forums/BlitzMax Programming/FOR bug

MrCredo(Posted 2007) [#1]
j=20
For i=1 To j
	Print "max:"+j+" current:"+i
	j=j-1
Next


run this code

"j" in for is not updated - i don't know why, but it should

test above code in blitzplus - last output should be "max:11 ..."


#include <stdio.h>
#include <stdlib.h>

int main()
{
    int i;
    int j=20;

    for (i=0; i<j; i++)
    {
        printf("max:%i%s%i\n",j," current:",i);
        j--;
    }
}


test this c code - last output should be "max:11 ..."


XPsp2


Yan(Posted 2007) [#2]
This is by design, 'j' is only evaluated at the start of the loop (unlike previous incarnations of blitz).

Use While/Wend or Repeat/Until for this kinda thang.


Isn't this in the docs?

[edit]
Yes it is, got to 'Language>Program Flow' and in the 'For/To/Next Loops' section, ye shalt find...
The exit value is only evaluated once at the start of the loop. This means it is safe to use complex or 'slow' code as an exit value.
[/edit]


MrCredo(Posted 2007) [#3]
hm i don't like this "design" - this is non-standard


GfK(Posted 2007) [#4]
It makes perfect sense. The "to" part must be a constant.

If you aren't happy with that, then as Yan said, use While/Wend, or Repeat/Until, depending on whether you want the expression evaluated at the start or end of the loop.


Brucey(Posted 2007) [#5]
Works for me too :-)


H&K(Posted 2007) [#6]
The for in C isnt really the same as a for in Basic.

HOWEVER, I agree with you, the Bmax result is... Unexpected if you have been using something else latly.

I mean B3D would give 11 as the max....

I think
1) It was a bug, which was co-opted as a feture
2) It needs serious defence, on speed grounds
3) I Prefer it.

@GFK and Yan.
I agree with both of you, but when all said and done it is a deviation from the BASIC standard that BRL have used, and some reason for the change would be good.

@MrCreda.
Ive never trusted these "POP/Exit the loop" variable changes, and before I used it in any code, I would generaly test it to see what the Language did. I use BMax to exclution now, and its handleing seems natural, but I can see that it isnt obviously so.


MrCredo(Posted 2007) [#7]
the problem is that FOR works with "constants" but j is not a constant (you understand?) - and you can't trust the result - this makes for-loop not usable.

for i=0 to array.length-1
  print array[i]
  array=array[..array.length-2]
next


codes like this are buggy in blitzmax - and this should signal that here is something wrong with for. yes i can use other loops, but i call this workaround.

=EDIT=

BlitzMax should detect automaticaly "constant" or "variable" as select optimized routines...


Who was John Galt?(Posted 2007) [#8]
I guess it's a matter of preference, but I like Max loops just the way they are. Modifying for loop variables in the loop makes for hard to read code.


Yan(Posted 2007) [#9]
Whether it's 'standard' or not, this is the way BMax works.

It's the way it's always worked.

It's documented.

It's not gonna change.

There are viable alternatives for what you're trying to do, even if you see them as 'workarounds'.

Stop worrying about it, have a nice cup of tea and get on with your coding. :o)


marksibly(Posted 2007) [#10]
This is by design.

It means you can have quite complex/slow expressions as the 'To' value and not have to worry about them being re-evaluated every loop, eg:

For Local x=0 Until ReallySlowFunction()


JoshK(Posted 2007) [#11]
This is cool, so you can do this:

For v=0 to CountVertices()-1

Instead of:

verts=CountVertices()
for v=0 to verts


You can even do this:

For n#=5 To 0 Step -0.5
Notify n
Next


Grey Alien(Posted 2007) [#12]
Yeah I like it this way. I'd use a While Went or Repeat Until if I say had a list and was removing items until it was empty.


H&K(Posted 2007) [#13]
I personaly prefer it this way as well, and as Ive said I never assume anything about what happens in any lang when you mess with the Index or the condition


Andrew Rollings(Posted 2007) [#14]
I dunno... It's very counter-intuitive. I would call that a bug... In every other language I can think of, if you don't want to re-evaluate every loop, you store it in a variable outside the loop...

It seems a big sacrifice to make for a small convenience.

Andrew


Koriolis(Posted 2007) [#15]
It seems a big sacrifice to make for a small convenience.
I disagree. Which sacrifice? How often do you modify the ending index of a for loop? If the answer is "very often" then I'll bet you do that to prematuraly exit the lopp. And frankly that's not a very good idea IMHO.
On the other hand, not having to worry about declaring and initialize a variable before the loop - for the sake of avoiding duplicate unneeded evaluations - is certainly a nice little plus on the code clarity side.


ziggy(Posted 2007) [#16]
In addition to the exposed beneffits, I supose that evaluating the expression "to" once, can let the compiler store the result (if it is an integer) in a processor register, wich is faster than accessing a regular variable.


_33(Posted 2007) [#17]
The only problem I find with the For statement which irritates me if that someitmes I would like to have a Step a, where a would either be -1 or +1, but that doesn't work.


TaskMaster(Posted 2007) [#18]
You can use Step, I do it all the time...

Edit: Oh wait, you mean you actually want to use a variable in the Step command? Strange, I can't see a need for that, but I guess anything is possible...


TomToad(Posted 2007) [#19]
I can see a need for a variable step size. One example is drawing a grid
For Local X:Int = 0 to 800 Step Size
    DrawLine X,0,X,600
Next
For Local Y:Int = 0 to 600 Step Size
    DrawLine 0,Y,800,Y
Next

Size would be the size of your grid. Except this code will not work since the step portion must be a constant. So instead, I have to do something like this.
Local X:Int = 0
While X < 800
    DrawLine X,0,X,600
    X :+ Size
Wend

Local Y:Int = 0
While Y < 600
    DrawLine 0,Y,800,Y
    Y :+ Size
Wend



H&K(Posted 2007) [#20]
For Local X:Int = 0 to 800/Size
    DrawLine X*size,0,X*size,600
Next
For Local Y:Int = 0 to 600/Size
    DrawLine 0,Y*size,800,Y*size
Next

However I agree that I dont like step needing to be a const. I dont maind it being evaluated once, but why can I not have a variable


Blueapples(Posted 2007) [#21]
This behavior is standard BASIC behavior and has been for 40 years. BRL aren't going to change it.


H&K(Posted 2007) [#22]
This behavior is standard BASIC behavior and has been for 40 years. BRL aren't going to change it
Which?
Because "to" is different between B3D and BMax. So it was changed at some point.


Blueapples(Posted 2007) [#23]
Sorry, I mean the single evaluation of the limit expression. The properties of TO are often unique to different dialects, though personally I prefer QuickBasic style. A variable step seems very un-BASIC, why not just use While? I'd be for it if the step value was evaluated *once*, just like the limit value.

Things like this scare me though, if the Step value was evaluated on every loop:

For Local X:Int = 0 to 800 Step Size
    DrawLine X,0,X,600
    Step :+ 1
Next


This is far too subtle. Much better to be more obvious and use While or some other loop.


H&K(Posted 2007) [#24]
Direction = 1 or -1
For x = StartPoint to StartPoint+Direction*Length step direction
    Plot or do STuff
Next
JetPac laser (for example)
I have no problem with the expression being evailuated once, but I do think that I should be able to change the step before this evailuation.
I used to be able to do it on the spectrum, and whilst I accept that I can/should use some of these newer loops, I have often instintivly used a for to step.


Czar Flavius(Posted 2007) [#25]
Object-orientated is very un-BASIC but it's in BlitzMax. I don't see what's so evil about a variable step. I've needed it once, and had to make messy while loops instead. If you don't want to use it, then don't. The compiler can easily detect whether the step is constant or not and change the code accordingly.


Blueapples(Posted 2007) [#26]
Object-orientation is a straw man argument: that feature is an addition, not a modification. Changing expectations (beyond adding new features) is a bad idea. It isn't BASIC if the core stuff doesn't behave as expected.


H&K(Posted 2007) [#27]
Re STep
Mr Blueapples you seem to be under the impressiom that this is a change that isnt already in some other Basics. It is in some other Basics. Its not changing expectations, because, as I said in this thread
I would generaly test it to see what the Language did

Its also a change that woulnt effect anything already written.


Czar Flavius(Posted 2007) [#28]
It's adding functionality that wouldn't affect anybody who didn't know about it. It's kind of hard to accidently modify a variable expecting it to be a constant, unless you didn't know what you were doing.


TomToad(Posted 2007) [#29]
Every BASIC I've used allows a variable for a step size. I also checked with Visual Basic and, sure enough, even that allows a variable step size. BlitzMAX is the first BASIC variant I've used that only accepts a constant for a step size.
Here is the original manual for the first BASIC. http://www.bitsavers.org/pdf/dartmouth/BASIC_Oct64.pdf
Notice the example on page 22. Definately using a variable for the step size.

There are several advantages to a variable step size. One is speed. The step has to be evaluated only once. If you must use a While..Wend construct, then the step must be evaluated every time through the loop.
Second, it just looks and reads better. Look at my two examples above. It is much easier to understand what is going on in the For..Next..Step loop rather than the While..Wend loop. It gets even worse when the loops get more complex.
I see no advantages to restricting the step size to a constant.


Czar Flavius(Posted 2007) [#30]
Actually I would have thought a constant step size would have been faster, as accessing a constant is quicker than getting a variable from memory. But I don't see how it would be difficult for the compiler to detect whether it is a constant or variable and change the code accordingly, no differently to detecting a = b + 1 from a = b + c.


H&K(Posted 2007) [#31]
One is speed....

Actually I would have thought a constant step size would have been faster
What I belive Tom was refering to was not the comparison between a variable step(evaluated once), and a constant step, but rather that a for loop with a variable step would be quicker than the alternative method needed to achieve this, specified by Tom (for example) as a While..Wend loop


TomToad(Posted 2007) [#32]
I was basically stating that a variable in a For..Next..Step loop would be faster than using a variable in a While..Wend loop, because since the parameters are only evaluated on entering the loop, you only need to grab the value from memory once. From there, it can be grabbed from the stack or a register. But since the values in a While..Wend loop are evaluated each loop, and since the values can change, then they must be grabbed from memory every loop.
Actually, the only slowdown with a variable instead of a constant in a For..Next..Step loop would be on entering the loop. After that, there shouldn't be no difference in the speed of a constant and a variable, since the variable would've been reduced to a constant after evaluation.


SculptureOfSoul(Posted 2007) [#33]
This reminds me why I love Lisp so much. Extending the language to include a customized for loop as in the suggestions above would take you all of 5 minutes with a macro.


skn3(Posted 2007) [#34]
I like it the way blitzmax does it!

It was something that wasn't clear as to which way (never done any tests).

Would be good if this could be a strict / non strict implementation though! Best of both worlds.


N(Posted 2007) [#35]
Alright, we know this isn't a bug, the mechanics shouldn't be changed because this is how Blitz has always worked, and you should definitely use a While loop if you want to modify variables the loop depends upon so that you know what's happening. Why hasn't this thread died, then?


SoggyP(Posted 2007) [#36]
Hello.

Just to add to the mix, I thought While/Wend was faster than For/Next? I can't confirm that being in work and I may have been drunk/dreaming/deceased at the time but I'm sure While is faster.

Goodbye.


grable(Posted 2007) [#37]
Just to add to the mix, I thought While/Wend was faster than For/Next? I can't confirm that being in work and I may have been drunk/dreaming/deceased at the time but I'm sure While is faster.

Actually, they compile down to the exact same assembler output ;)


Blueapples(Posted 2007) [#38]

Actually, they compile down to the exact same assembler output ;)



I'm quite sure that they do not. That's the point of a large portion of this conversation; While evaluates it's condition on every iteration, but For's upper limit is evaluated once and then that unchanging result is compared to the For variable on each iteration.

Those two things cannot possibly result in the same ASM.


Blueapples(Posted 2007) [#39]

I see no advantages to restricting the step size to a constant.



I totally agree. I think I missed a post or two, what I understand the term "variable" to mean is usually that the value varies, meaning, I was reading various arguments as suggesting that the step value could change during the loop. I hope that's obviously a bad idea.

So I agree, but actually I think the step value should be an expression, not a constant or variable, or any other specific item, just any kind of expression. Provided that that expression is, like the limit, evaluated once when the loop is entered.


grable(Posted 2007) [#40]
I'm quite sure that they do not.

Asside from the constant eval for for loops, they do.
But dont take my word for it, check it out for yourself.


Blueapples(Posted 2007) [#41]

Asside from the constant eval for for loops, they do.
But dont take my word for it, check it out for yourself.



Hmmm. You're right. That's just... wrong, but I guess it makes sense in a way. Given this, I would be afraid how they might end up implementing support for expressions/variables as step arguments in for. That would probably lead to exactly what I think is a terrible and bug causing behavior: allowing the step value to change during a loop.


Both types of loops look like this (with a "I :+ 1" inside the While loop):

	mov	eax,10

_6:
	add	edx,1
_4:
	cmp	edx,eax
	jle	_6


So, it would seem that For actually does not use a constant eval, since it is just taking the value from eax without any kind of checking. I added "I :- 1" to the loop, and it just modifies eax merrily without caring that it's in a For loop.

The While however does prefix the loop with this:

_2:
	mov	edx,1
	jmp	_4


So, technically, it does do three more instructions (jmp, cmp, jle) than the equivalent For loop. But if anyone is worried about three CPU instructions, they shouldn't be writing their code in BMax in the first place.

BMax code used to get this ASM:
Framework BRL.Blitz
Local I:Int
Local Limit:Int = 10

For i = 1 To Limit
i :- 1
Next

i = 1
While i <= Limit
	i :+ 1
Wend



tonyg(Posted 2007) [#42]
... at least nothing that'll make a speed difference even over 100000s of loops.


Blueapples(Posted 2007) [#43]
Even better, they really are exactly the same. I hadn't noticed a couple instructions, the for loop does the same jump and initial check. So... no difference. Nuts, I guess I really should look into things before just blinding making claims like that.

To be fair, this isn't the behavior I'm used to.


Czar Flavius(Posted 2007) [#44]
Fifty Percent More, maybe this thread hasn't died because whether you can see it or not, people want that extra percent more ironically. A good programming language doesn't deny you flexability for arbitary reasons, and some examples of its usage have been given.

mechanics shouldn't be changed because this is how Blitz has always worked


Then go back to Blitz Basic 1, whatever that was.....

A step which can be anything but isn't changed during the loop is what I have needed before, but had to use clumsy while loops (nested too) to achieve the effect.

If people want to use a varying step inside the loop I don't see why not. What do you mean by bugs? Such a simple thing wouldn't cause any random Blitz Basic bugs.

If you're referring to bugs the programmer makes, then if they really wanted to they could do the same with equivilent while loops if they really wanted to (which would probably cause more bugs due to less readability) so you're not protecting anybody by disallowing it.

If you don't want to use varying steps, that is fine. But what's so great about this addition is that all previous code would work the exact same way. If steps that could vary were secretly added, you would never know (unless you tried it). So it's not like we are purposing a radical change that would mess up people's code.

So in summary, if the task can already be accomplished, but using a less tidy method, and a tidy method is disallowed for just an arbitary reason, then why not allow it? Why so much resistance? Want Blitz to work as it aways has? Then just don't use it :)


N(Posted 2007) [#45]
Fifty Percent More, maybe this thread hasn't died because whether you can see it or not, people want that extra percent more ironically.
Good for you, I'm sure you understand every aspect of this.
A step which can be anything but isn't changed during the loop is what I have needed before, but had to use clumsy while loops (nested too) to achieve the effect.
So you're saying that because you know exactly where something is happening, it's clumsy?
If people want to use a varying step inside the loop I don't see why not. What do you mean by bugs? Such a simple thing wouldn't cause any random Blitz Basic bugs.
If you're talking to me, I said no such thing about it causing random bugs. I'm simply talking about mechanics that, if changed, would result in slower loops (something you don't seem to understand).


H&K(Posted 2007) [#46]
I said no such thing about it causing random bugs. I'm simply talking about mechanics that, if changed, would result in slower loops (something you don't seem to understand).
I dont think that step being a Variable/expression which is analysed once would make it a slower loop. It would (probably) make the setup of the loop take one maybe two cpu cycles slower, but once in the loop, the loop would take the same amount of time.

If the expresion was to be analyised each loop then yes ok slower each loop, but int the way I would like it, the Step once computed will be available at just the same speed.

the mechanics shouldn't be changed because this is how Blitz has always worked
As had been pointed out before and after your post, For next step has not been consitant across the blitz range

So you're saying that because you know exactly where something is happening, it's clumsy?
That makes no sence? What was being said was that as many of us used to do it like that in prvious Basics, we tend, without analysing it, to do it that way again
, yet it no longer works

I say this in all seriousness, but you can probaly see a better way to do things with the more modern commands that have always existed for you. However in the Basic that I, (and probaly others) learnt "for to step" was practicly the only loop we had, (excluding goto/gosub). And I nearly always approach a index loop problem by trying a for first.
This doesnt mean thta you are not correct, amd that the for loop should be streamlined to be the fastest, but it deosnt surly preclude the possibility that a more general for loop structure might be of more productive use


TomToad(Posted 2007) [#47]
@BlueApples: Your simple example does not properly illustrate where While..Wend will slow down the execution of loops over For..Next.
Using this code:

Produces this for the For..Next loop

And this for the While..Wend loop

It is conceivable that the return values of function calls would be used as part of the exit test, and that could result in a considerable amount of time difference with the WHile..Wend loop over the For..Next loop.
Now if the Functions are to be only evaluated once, such as it is in the For..Next loop, you could use this:
Local I:Int = 0
Local Temp:Int = (GetLowValue() + GetHighValue()) * GetMultiplier()
While I <= Temp
	Print I
	I :+ 1
Wend

Which produces this:

Nearly identical to the For..Next loop, but the loop just gets uglier and uglier. Also the more you must implement yourself, the more likely that bugs will creep in. When I first compiled the two examples above, I forgot to include the I :+ 1 in the While..Wend loop, a problem that would not occur in the For..Next loop.

ETA: The above is compounded when adding the Step value to the equation, which is where I have problems with the implementation of For..Next.


Czar Flavius(Posted 2007) [#48]
So you're saying that because you know exactly where something is happening, it's clumsy?


So why write a program using functions when you can use goto and gosubs? After all, if you know exactly what's happening, it's not clumsy is it.

If you're talking to me, I said no such thing about it causing random bugs.


I was refering to Blueapples.

if changed, would result in slower loops (something you don't seem to understand).


Like I said,
The compiler can easily detect whether the step is constant or not and change the code accordingly.
And this is proved by the fact the compiler complains if you use a non-constant. And what I meant was the compiler can be changed/modified to do that, rather than it can do so already, in case you misunderstand me.

So if the step value is constant, like it is now, then it treats it as the for loop is now. However if the step is a variable, it creates it as a new, though slower, kind of loop. But using the for loop in the "standard" way will still not cause any slowdown.

What exactly have I misunderstood? Please enlighten me.


Otus(Posted 2007) [#49]
I dont think that step being a Variable/expression which is analysed once would make it a slower loop. It would (probably) make the setup of the loop take one maybe two cpu cycles slower, but once in the loop, the loop would take the same amount of time.
I think it would. A variable/expression would have to be evaluated at run time, so the compiler could not optimize the loop as efficiently.(See http://en.wikipedia.org/wiki/Loop_unrolling )


Koriolis(Posted 2007) [#50]
Well as Czar Flavius said, it's easy for the compiler to handle both cases differently: the case where the expression is constant, and the case wher it's not. So it's not like it's not doable. The question is, is it really an improvement to have a construct called "for" that is actually a disguised general loop? Any loop that has a variable step has little to do with my idea of a "for" loop, and the principle of least astonishment should apply here: if it doesn't feel like a for loop, then maybe it shouldn't look like it. It will cerainly be easier to read and maintain for me.
That's naturally totally personal, but I think it applies to most people, wether they realize it or not.


H&K(Posted 2007) [#51]
A variable/expression would have to be evaluated at run time
Once at the commensment of the loop, after which it would be effectivly a const. So no I disagree that the loop itself would now not be as efficient.


marksibly(Posted 2007) [#52]
Hi,

The 'const step size' issue was discussed back in the B3D days, but I'll explain why it's the way it is again.

The problem is that the sign of the step size affects the comparison code needed to 'terminate' the loop - ie: greater-than if step is >0, less-than if it's <0.

To do this 'on the fly' is pretty ugly - it'd look something like this (emulating for/next with repeat):
Local _from=1
Local _to=10
Local _step=GetVariableStep()
Local _index=_from
Repeat
  '
  ' yuck...
  If _step>0
    If _index>_to Exit
  Else
    If _index<_to Exit
  EndIf
  '
  'your code here...
  '
  _index:+_step
Forever


But you generally know when writing the code whether the comparison should be > or <, so the code is less efficient than writing your own 'while' or whatever - and I don't particularly like the compiler generating less efficient code for the sake of convenience.

The problem is really to do with the lack of enough information in BASIC's for/next construct. For example, C makes you explicitly provide the terminating condition:

//will not work well if step is <0!
for( _index=1;_index<=10;_index+=step ){...}

However, I agree that it's inconvenient, and perhaps I should relax things a bit here? The slow/wonky comparison would only need to be generated for non-const steps anyway.

Another option would be to generate 2 'versions' of the entire loop, and select which one to use at loop entry. This would be efficient, although a little nasty for me to mung into the compiler!


H&K(Posted 2007) [#53]
Local _from=1
Local _step=GetVariableStep()
Local _index=_from
Local _to=10* Sgn _step
Repeat
  '
    If _index* Sgn _step>_to Exit

  '
  'your code here...
  '
  _index:+_step
Forever
But I admit that would slow the loop more than I had been claiming