Compile time data structure

H. S. Teoh hsteoh at quickfur.ath.cx
Thu Sep 19 12:53:30 PDT 2013


On Thu, Sep 19, 2013 at 12:46:39AM -0700, Ali Çehreli wrote:
> On 09/17/2013 07:26 PM, H. S. Teoh wrote:
> 
> > On Wed, Sep 18, 2013 at 04:12:01AM +0200, John Colvin wrote:
> >> On Wednesday, 18 September 2013 at 01:24:37 UTC, Ali Çehreli wrote:
> >>> As far as I know, static foreach is only for tuples (or TypeTuples)
> >>
> >> TypeTuples are tuples. Sortof. We really need to get that whole
> >> situation sorted....
> >
> > Not to mention there are also "parameter tuples" that behave sorta
> > like TypeTuples but not quite.
> 
> I have just published two chapters that present tuples and TypeTuple
> sufficiently-completely and sufficiently-consistently (of course,
> according to me ;) ):
> 
> Tuples:
> 
>   http://ddili.org/ders/d.en/tuples.html
> 
> More Templates:
> 
>   http://ddili.org/ders/d.en/templates_more.html
> 
> I haven't officially announced those yet. I appreciate any review.
> (I am sure there are lots of English grammar and syntax issues as
> well. :-/ Those mature over time organically. :) )
[...]

I didn't read everything in detail but skimming over it, I think the
chapters look OK.

But they didn't describe parameter tuples -- and I'm not sure if you
want to, because it's very dark arcane magic. But if you *really* want
to know:

A parameter tuple is the type of Params in the following code:

	import std.stdio;
	
	// Function to analyze
	int func(string x, int y=123, float z=1.618) {
		return 0;
	}
	
	// Prints out types, names, and default arguments of func.
	void main() {
		static if (is(typeof(func) Params == __parameters)) {
			foreach (i, param; Params) {
				writefln("[%d]:", i);
				writeln("\tType: ", param.stringof);
				writeln("\tName: ", __traits(identifier, Params[i..i+1]));
	
				auto getDefArg(int i)(Params[i..i+1] args) {
					return args[0];
				}
				static if (is(typeof(getDefArg!i())))
					writeln("\tDefault value: ", getDefArg!i());
			}
		}
	}

This code looks pretty innocent until you start looking at it closer.

For starters, the first line in main() must be written *exactly* like
that, because the magic keyword __parameters *only* works in this exact
invocation of is(), and doesn't work anywhere else in the language. This
must be one of the darkest corners of is() syntax, because there's
basically no consistent rationalization of what each element in `is(X Y
== Z)` actually means. Each different Z changes the interpretation of X
and Y in unpredictable ways (cf. the language spec on dlang.org, under
Expressions -> isExpression -> 6).

Then there's the main question of what exactly Params is. It's what the
docs call a "parameter tuple", but what is that? Well, first of all,
contrary to what one might expect, calling foreach over Params does NOT
actually iterate over its actual elements. Rather, it only iterates over
the *types* of each func's parameters (i.e., string, int, float).

This is why in the next line, `param.stringof` prints only the type name
of the parameter.

So how does one get the name of the parameter? Well, a first thought
might be, since foreach doesn't give us actual elements in Params, maybe
array-indexing notation (i.e., Params[i]) might? Wrong! Params[i] also
returns only the *type* of the parameter. :)  It turns out that the only
way to actually get an element of Params that isn't reduced to just a
type is to take a 1-element slice of it. That's why we have to write
Params[i..i+1] in the code. Are you confused yet? :) Once we have it,
though, how do we get the parameter name? Well, it turns out that
Params[i..i+1].stringof returns strings of the form "(int y = 123)"
which we'd have to manually parse. To avoid needing manual string
parsing, we have to use __traits(identifier...) to get the parameter
name. (Of course! Isn't that obvious!?)

Finally, what about default arguments? We *could* in theory parse
Params[i..i+1].stringof to extract it as a string, and then use mixin()
to get at its actual value... but I don't feel very confident that my
string parsing code would actually be correct in all possible cases, so
I'd rather have the compiler tell me what the default value is directly.
Except that there is no way to directly get the default value at all!
The only way (and this I learned from Phobos, since it's pretty much
impossible for anybody to guess on their own) is to declare a function
using Params[i..i+1] -- which, being a parameter type tuple, obviously
can be used to declare function parameters -- that returns the value of
the default parameter.

The additional (int i) compile-time argument was added to appease the
compiler, because otherwise each iteration of the foreach loop would
declare a different function body under the same name "getDefArg", which
wouldn't work. So we templatize the function with the loop index in
order to generate unique names for its various incarnations.

Now, how do we know if the parameter has a default value? Well, it has a
default value if getDefArg!i can be called with no arguments, you see,
since writing getDefArg(int i)(Params[i..i+1] args) is equivalent to
writing getDefArg(int i)(int y=123) when Params[i..i+1] represents the
parameter `int y=123`. So if the default parameter is present, getDefArg
can be called with no arguments. So that's what the static if checks
for.

But here, `args` binds not to the parameter itself, but to the 1-element
tuple representing the argument list, so to get the actual default
value, we have to return args[0], not just args. (Of course! Isn't that
obvious??!)


And that concludes today's lesson on parameter tuples. I hope you're
thoroughly confused and utterly perplexed by now, because next class,
we're going to talk about how to extract parameter qualifiers (like ref,
scope, etc.) from a parameter tuple when the language actually has no
API to directly fetch this information. :-P

(On a more serious note, though: all of the above probably should *not*
be any D textbook; readers should instead be directed to std.traits
where all of this arcane black magic is wrapped by a nicer user-facing
API that isn't insanity-inducing.)


T

-- 
Маленькие детки - маленькие бедки.


More information about the Digitalmars-d-learn mailing list