Haxe: Some cleaner C-style for-loops

This is a bit of a continuation to my older post on the matter of migrating C-style for-loops from other languages to Haxe, expanding with some new things I've learnt since then.

Similar to some older solutions, this involves a bit of a macros, but both the macros and the results got cleaner.

First, let's look at the "everything is complicated" use case again:

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

Due to mix of iterator modification and iteration skipping, it would previously be quite a trouble, requiring manual handling of each case (or coping with weird macro-made code). Now, however...

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

You can clearly say that this smells of macro, but let's look at the resulting code:

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

That is pretty good, isn't it? Conversion is automatic, there is no additional code, and it retains readability as well! And all it took was using the regular (otherwise faulty) approach while replacing continue statements with blocks containing the "afterthought" and the actual statement.

And the macros handling this is not too scary either:

static macro function cfor(init, cond, post, expr) {
	#if !display
	var func = null;
	func = function(expr:haxe.macro.Expr) {
		return switch (expr.expr) {
		case EContinue: macro { $post; $expr; }
		case EWhile(_, _, _): expr;
		case ECall(macro cfor, _): expr;
		case EFor(_): expr;
		case EIn(_): expr;
		default: haxe.macro.ExprTools.map(expr, func);
		}
	}
	expr = func(expr);
	#end
	return macro {
		$init;
		while ($cond) {
			$expr;
			$post;
		}
	};
}

The first part of the function does the replacement trick described while being cautious to note replace anything in sub-loops on accident. Then there's the actual restructuring of function arguments into a block with a loop.

"fast method access" is accomplished by simply importing the method from a class.

import Utils.cfor;

While this approach still does not exactly "feel like home", it is simple, portable, and transparent in whether it works or not (unlike @:metadata, which fails silently if the macros did not execute), so I think it's a worthy substitute for the time being.


It is also worth mentioning that this approach is largely inspired by Patrick Le Clec'h's "hacking haxe" repository, which in particular has a commit that adds a similar thing on the language level (and with convetional syntax - example).

So the absence of the C-style for loops in Haxe is likely not a technical problem but a conscious choice of exclusion. Of course, it's just convenience. But so are the commonly-discussed short lambdas. Or many of numerous existing language features. Development tools are all about convenience, and convenience is why one gets chosen over another. This is why we don't program things in assembly, you see?

Related posts:

One thought on “Haxe: Some cleaner C-style for-loops

  1. Pingback: Haxe: Shorthand expression matching

Leave a Reply

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