DMD 0.170 release

Tom S h3r3tic at remove.mat.uni.torun.pl
Tue Oct 17 10:02:02 PDT 2006


Lars Ivar Igesund wrote:
> I'm just trying to play the devil's (that's Tom) advocate here ;) What I
> think was shown by Tom, wasn't that the new feature isn't damn useful, but
> that there might be better, more flexible and more powerful ways to
> implement the feature, if it is possible with templates, then the sugar
> shouldn't be any worse, and the possibilities one could gain for D in
> general could be substantial.


Thanks, Lars :) Indeed, I'm not saying that the feature is superfluous, 
I'm just trying to find a better way. For instance, the older 'trailing 
delegate' proposal could make foreach obsolete by allowing totally 
custom foreach-alike loops to be created. Last time I mentioned it, 
Walter seemed to like the proposal, but there was one open problem - 
namely that if I had code like:

void myLoop(void delegate() dg) {
	dg();
	...
	dg();
}

myLoop {		// special syntax sugar for trailing delegates
	return;
}


then the 'return' would actually only return from the delegate, not from 
the scope that encloses myLoop. This and break wouldn't work for custom 
loops.

But after a while of brainstorming on #d, I return with another 
proposal. D could solve the problem by more or less translating the code:

----

void each(int[] a, void delegate(int) dg, inout void* ret) {
	for (int i = 0; i < a.length; ++i) {
		dg(a[i]);
		if (ret) return;
	}
}


char[] foo() {
	int[] arr;
	arr ~= 5;
	arr ~= 3;
	arr ~= 2;
	arr ~= 6;

	each (arr) (int a) {
		if (1 == a) return "blah";
		if (2 == a) return "hmm";
		if (3 == a) break;
	}

	return null;
}


void main() {
	char[] str = foo();
	printf("foo returned: %.*s\n", str);
}

----


into:


----

void* CONTINUE	= cast(void*)0;
void* BREAK		= cast(void*)1;


void each(int[] a, void delegate(int) dg, inout void* ret) {
	for (int i = 0; i < a.length; ++i) {
		dg(a[i]);
		if (ret) return;
	}
}


char[] foo() {
	int[] arr;
	arr ~= 5;
	arr ~= 2;
	arr ~= 3;
	arr ~= 6;

	{
		char[]	retCode;
		void*	ret;

		each(arr, (int a) {
			if (1 == a) { retCode = "blah";	ret = &retCode; return; }
			if (2 == a) { retCode = "hmm";	ret = &retCode; return; }
			if (3 == a) { ret = BREAK; return; }
		}, ret);

		if (ret !is BREAK && ret !is CONTINUE) return *cast(char[]*)ret;
	}

	return null;
}


void main() {
	char[] str = foo();
	printf("foo returned: %.*s\n", str);
}

----


Ok, what happened there ? The 'each' function takes two special parameters:
1. a delegate with no return
2. an inout void*:
in this case, `void delegate(int) dg, inout void* ret`.

They define the trailing delegate - 'dg' is the delegate's body and 
'ret' is a special value used to communicate with the enclosing scope. 
The void* could be moved to the function and delegate's return type, but 
in that case, the custom loop function couldn't return anything to the 
enclosing scope. With this approach, e.g. the number of iterations can 
be returned.

There's nothing special about the 'each' function itself, the only magic 
happens at its call site. In the custom loop's body,
each 'break' is converted into '{ ret = BREAK; return; }',
each 'continue' into '{ ret = CONTINUE; return; }'
and each 'return X' into '{ retCode = X; ret = &retCode; return; }

After the custom loop returns, its 'ret' is checked to see whether the 
return from the loop (in this case, the 'each' function) was a CONTINUE, 
BREAK or a return from the enclosing scope. In the latter case, that 
value is returned from the call site.

I hope I've covered every spot, but the proposal may still have bugs... 
Anyway, thanks goto Oskar Linde for helping me come up with this on #d !


--
Tomasz Stachowiak



More information about the Digitalmars-d-announce mailing list