Haxe: migrating For-loops

Migrating from C-styled for-loops to Haxe for-loops

If you are migrating to Haxe (or just porting an existing application) from another C-like language, such as JavaScript or ActionScript, you may have already noticed that for-loops in Haxe differ a bit from standard format that you would expect to see in languages of this type. To be precise, they lack common 3-statement format support. This article is dedicated to different methods of "migrating" your for-loops without rewriting contents entirely.

Standard loops

Fortunately, there is a standard "..." structure in Haxe to allow "for (i = a; i < b; i++)" loops to be ported easily. That means that what would have been this in JavaScript,

for (var i = 0; i < 10; i++) {
	trace(i);
}

Becomes this in Haxe,

for (i in 0 ... 10) {
	trace(i);
}

Non-standard loops

Well, that was an easy case. If you want a loop going backwards, however, this is not as easy. You may want to use an iterator. Noone seems to like using iterators for number-based loops, but not about that. So to convert this JavaScript code

for (var i = 9; i >= 0; i--) {
	trace(i);
}
We will first need to implement a small iterator like this one:
class DecIter {
	var now:Int;
	var limit:Int;
	public function new(max:Int, min:Int) {
		now = max;
		limit = min;
	}
	public function hasNext() { return (now >= limit); }
	public function next() { return now--; }
}
And then you can use it easily:
for (i in new DecIter(9, 0)) {
	trace(i);
}

A downside is, as you may've guessed, that multiple iterator classes will have to be created for different situations.

Update: as it has been pointed out, you can also just invert iterating both variable and range to make a countdown loop.

Talking of iterators

In some cases migrating for-loops is harder. Though it can also happen to be easier than expected. For example, if you're iterating over an array,

for (var i = 0; i < array.length; i++) {
	trace(array[i]);
}
the Haxe code is actually going to be shorter than it's JS counterpart:
for (v in array) {
	trace(v);
}

Similar iterator usage also applies to many standard types including Arrays, Maps (previously Hashes), Enums, certain Reflect methods... overall, if you are looping through contents of something, it's worth checking - it might be already covered "out of box".

Flow control

Now, let's take a look on a slightly rarer case. Let's assume that you want to change the "iterating" variable in the middle of the loop for some reason. For example, to skip a couple of elements from range:

for (var i = 0; i < 10; i++) {
	if (i == 3) i += 3;
	trace(i);
}
And it would look like you could just do "i += 3" inside of Haxe for-loop as well, but... no, you can't. That's because your for-loop of this kind
for (i in 0 ... 10) {
	if (i == 3) i += 3;
	trace(i);
}
is actually converted into something of this structure:
var i0:Int = 0;
var i1:Int = 10;
while (i0 < i1) {
	var i:Int = i0++;
	if (i == 3) i += 3;
	trace(i);
}
And thus, changing "i" inside the loop has just about... no good effect.
Of course, one can insert a bunch of "continue" statements in this case...
for (i in 0 ... 10) {
	if (i >= 3 && i < 6) continue;
	trace(i);
}
Or, as an a better solution, a for-loop can be converted into a while-loop, like this:
var i:Int = 0;
while (i < 10) {
	if (i == 3) i += 3;
	trace(i);
	i++;
}
Now, if you don't look at a small scoping issue, this covers the given case pretty much perfectly... except one point.

"Everything is complicated"

Aforementioned code example works pretty nicely as long as you don't insert break/continue statements into your code. If added, such prevent the "post-iteration" code from being executed, and thus may break the program flow.
Indeed, code like this:

for (var i = 0; i < 10; i++) {
	if (i == 3) i++;
	if (i == 5) continue;
	trace(i);
}

Is the most complicated case, for which I haven't seen any automatic methods of conversion yet.
However, it is possible to convert/migrate even this. As fact, it is possible to migrate any for-loop... with a bit of hack.
A for-loop, generally, looks like this:

for (initialization; condition; afterthought) {
	action;
}
There is initialization, condition, and afterthought. It can be converted into a while-loop, but flow statements like break/continue ruin that. A "do-while" (repeat-until in some languages) loop, on other hand, has a condition checked after the iteration, which makes it more suitable than while-loop. It doesn't have a condition check at iteration start and initialization code, but we can get around that, actually:
initialization;
do {
	if (!(condition)) break;
	action;
} while (Std.is(afterthought, Dynamic) /* anything that evaluates as "true" */);
To clear up how the last line is working: since do-while loop has a "afterthought" condition, and most of operations also have a value returned, you can do whatever operation upon that yielding a "true" result, while having the actual exit condition sit inside the loop. This way the break/continue statements work correctly. If you have been "cool enough" to use a Void-yielding function call as an afterthought, you can also use a lambda expression like this:
	// ...
} while ({ afterthought; true; }));
Thus, the aforementioned JavaScript code can be converted into this:
var i:Int = 0;
do {
	if (!(i < 10)) break;
	if (i == 3) i++;
	if (i == 5) continue;
	trace(i);
} while (i++ >= 0 /* always true */);

Which is actually pretty good of approach... don't you think?

Conclusion

Despite of variety of structural presentations, all or almost all code can be migrated. There's no need to rewrite everything from scratch, especially in our age of automation.

Related posts:

9 thoughts on “Haxe: migrating For-loops

  1. Sorry for that it happens now.
    Before no problem – I have fixed and it works fine.

    Now it happens like bug.
    “Missing return: cpp.Void”
    If I write
    private function forEachInt<T>(fromVecInt:Vector.<Int>, fn:Int->T):Void
    {
    for (i in 0 … fromVecInt.length) fn(fromVecInt[i]);
    }
    than I write Main.hx
    var vec:Vector<Int> = new Vector<Int>();
    forEachInt(vec, function(i:Int):Void
    {
    vec[i] = 1;
    });
    var time:Int = Lib.getTime();
    trace(“forEachInt :”+(Lib.getTime()-time)+”ms”);
    If I compile than it shows error “Missing return: cpp.Void”. I don’t understand. How do I fix if cpp has bugged?

    • Sorry for that and thanks. I have fixed.
      Because I don’t see “import cpp.Void” that is why I need remove “import cpp.Void” and it is gone and fixed. Thanks for helping. My best! I am sorry for that. I don’t know before. Now I know that. Thanks my best Vadim!!!

  2. Eh where is forEach(function(….)) into Haxe.

    Example:
    var data:Vector.;
    data.forEach(function(ofs:int, pos:int, …):void
    {
    ….
    });
    How is conversion into haxe. Thanks!

    I don’t understand why Haxe has not methods of Array, Vector or ByteArray.

    Is it correct or wrong?
    Do you mean “non standard loops”?

    • Iterators replace forEach, but if you want that particular format, you could have something like

      public static inline function forEachV<T>(v:Vector<T>, fn:Int->T->Vector<T>->Void) {
          for (i in 0 ... v.length) fn(i, v[i], v);
      }

      and use it like forEachV(data, function(ofs:Int, value) { … })

      • Thanks for reply!
        Is it true?
        .forEach(function(val:Objject, n:int, dir:Vector:):void {…ยก});
        Ps: hidden corne clams are replaced out of your blog.
        Into
        private function forEachVDir(fromVObj:Vector, val:Dynamic, fn:Vector->T->Void){
        ….
        }
        Is it correct? Thanks for resolve ;) If it works than I let our solution.

        • Fixed the code sample – WordPress allows limited HTML in comments, so writing <T> assumes it to be a HTML tag, so you’d need to write &lt;T&gt;.

          If you move that to a separate class (say, named VectorTools), you could then add a

          using VectorTools;

          to the related classes, and would be able to do

          vector.forEach(function(...) { ... })

          as with APIs that have this built-in.

          • Hello Vadim, thanks
            I have question about from 1. comment
            public static inline function forEachV(v:Vector, fn:Int->T->Vector->Void) {
            for (i in 0 ... v.length) fn(i, v[i], v);
            }

            But I have written:

            var data:Vector;
            forEachV(function(data, i:Int):Void
            {
            trace("forEachV: "+v.length);
            });

            I got errors:
            “src/Main.hx:31: lines 31-33 : ofs : Int -> Void should be Int -> openfl.utils.ByteArray -> openfl.Vector -> Void”

            I don’t understand. It always get critic errors.
            I tried Vectortools. But I don’t know how do I get forEach and check VectorTools.hx has not method forEach();

            So hard to understand. I am still unhappy because I tried any solutions. No success…..

  3. Good reading, something important to take note on how it differs from regular ECMAscript/JS/as3, etc. The regular iterator “short-form” min..max form for Haxe is convenient in most cases (ie. no need to declare “immutable” boilerplate variable initializations before a loop), but some exception cases would require you to use a regular “while” loop convention instead to maintain state scope. I just wish they also had the regular “for” loop convention as well that allows some coders to define multiple initializations and afterthoughts in a single-line.

  4. Pingback: Haxe: Some cleaner C-style for-loops

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.