www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Compile time features

reply Shammah Chancellor <shammah.chancellor gmail.com> writes:
Currently D has some very very powerful compile time features, 
but they can get unwieldily quickly when doing compile time 
reflection with mildly complicated logic.  It's pretty 
disappointing.  I'd like to start a discussion around some of the 
problems I've ran into using these features.  If there aren't 
bugzilla reports, I can open them but I'm not entirely sure how 
to file these appropriately.

1) There seems to be many cases where __traits(allMembers, ...) 
over a package produces symbol strings which cannot be converted 
to a symbol using __traits(getMember). Also __traits is not 
useable in reasonable contexts( alias foo = __traits(..)). E.g.

This will fail due to weird symbols being returned from 
allMembers.
```
import std.stdio;

enum hack(A...) = A;

int main() {
	foreach(member; __traits(allMembers, std.stdio)) {
		pragma(msg, member);
		alias sym = hack!(__traits(getMember, std.stdio, member))[0];
	}
	return 0;
}
```

2) There are oddities around getting the types of functions which 
are properties:

e.g. (Note the __compiles, and addressing the member):

```
//Gets the member type, even for properties.
private template MemberType(C, string memberName)
{
     alias member = TypeTuple!(__traits (getMember, C, 
memberName))[0];

     static if (__traits (compiles, typeof(&member)))
     {
         static if (isSomeFunction!(typeof(&member)))
         {
             alias MemberType = typeof(&member);
         }
         else
         {
             alias MemberType = typeof(member);
         }
     } else {
         alias MemberType = typeof(member);
     }
}
```

3) Additionally, any kind of iteration becomes very unwieldy.  
This kind of code becomes necessary in common cases.  (E.g. pass 
in a list of packages and reduce to a AliasSeq of type symbols)


```
import std.stdio : writeln;
import std.typetuple;

// This won't work:
/+
private template stuff(Args...) {
	/+ static +/ foreach(Arg; Args) {
		...
	}
}
+/

// Must do this instead:
private template expand(Args...)
{
	static if (Args.length > 0)
	{
		static if (Args.length > 1)
		{
			enum stuff = AliasSeq!(repeat!(Args[0], Args.length), 
stuff!(Args[1..$]));
		}
		else static if (Args.length == 1)
		{
			enum stuff = AliasSeq!(repeat!(Args[0], Args.length));
		}
	}
}

private template repeat(alias T, int times) {
	static if ( times > 1 ) {
		enum repeat = AliasSeq!(T, repeat!(T, times-1));
	} else static if (times == 1) {
		enum repeat = AliasSeq!(T);
	}
}

enum things = expand!("A","B","C");

void main() {
	pragma(msg, things);
	writeln("Hello World");
}
```
Oct 28 2015
parent reply qsdf <qsdf qsdf.qsdf.qsdf.fi> writes:
On Thursday, 29 October 2015 at 02:43:59 UTC, Shammah Chancellor 
wrote:
 Currently D has some very very powerful compile time features, 
 but they can get unwieldily quickly when doing compile time 
 reflection with mildly complicated logic.  It's pretty 
 disappointing.  I'd like to start a discussion around some of 
 the problems I've ran into using these features.  If there 
 aren't bugzilla reports, I can open them but I'm not entirely 
 sure how to file these appropriately.

 1) There seems to be many cases where __traits(allMembers, ...) 
 over a package produces symbol strings which cannot be 
 converted to a symbol using __traits(getMember). Also __traits 
 is not useable in reasonable contexts( alias foo = 
 __traits(..)). E.g.

 This will fail due to weird symbols being returned from 
 allMembers.
 ```
 import std.stdio;

 enum hack(A...) = A;

 int main() {
 	foreach(member; __traits(allMembers, std.stdio)) {
 		pragma(msg, member);
 		alias sym = hack!(__traits(getMember, std.stdio, member))[0];
 	}
 	return 0;
 }
 ```

 2) There are oddities around getting the types of functions 
 which are properties:

 e.g. (Note the __compiles, and addressing the member):

 ```
 //Gets the member type, even for properties.
 private template MemberType(C, string memberName)
 {
     alias member = TypeTuple!(__traits (getMember, C, 
 memberName))[0];

     static if (__traits (compiles, typeof(&member)))
     {
         static if (isSomeFunction!(typeof(&member)))
         {
             alias MemberType = typeof(&member);
         }
         else
         {
             alias MemberType = typeof(member);
         }
     } else {
         alias MemberType = typeof(member);
     }
 }
 ```

 3) Additionally, any kind of iteration becomes very unwieldy.  
 This kind of code becomes necessary in common cases.  (E.g. 
 pass in a list of packages and reduce to a AliasSeq of type 
 symbols)


 ```
 import std.stdio : writeln;
 import std.typetuple;

 // This won't work:
 /+
 private template stuff(Args...) {
 	/+ static +/ foreach(Arg; Args) {
 		...
 	}
 }
 +/

 // Must do this instead:
 private template expand(Args...)
 {
 	static if (Args.length > 0)
 	{
 		static if (Args.length > 1)
 		{
 			enum stuff = AliasSeq!(repeat!(Args[0], Args.length), 
 stuff!(Args[1..$]));
 		}
 		else static if (Args.length == 1)
 		{
 			enum stuff = AliasSeq!(repeat!(Args[0], Args.length));
 		}
 	}
 }

 private template repeat(alias T, int times) {
 	static if ( times > 1 ) {
 		enum repeat = AliasSeq!(T, repeat!(T, times-1));
 	} else static if (times == 1) {
 		enum repeat = AliasSeq!(T);
 	}
 }

 enum things = expand!("A","B","C");

 void main() {
 	pragma(msg, things);
 	writeln("Hello World");
 }
 ```
I agree with you on an aspect: writing code with __traits() often leads to a cascade of "unfriendly", "cryptic", "undigest", static if and loops. However your examples are discutables: 1/ The symbol is not always a type, so to test it there is `if(is()){}`: --- import std.stdio; enum hack(A...) = A; int main() { foreach(member; __traits(allMembers, std.stdio)) { pragma(msg, member); static if (is(member)) alias sym = hack!(__traits(getMember, std.stdio, member))[0]; } } --- 2/ with only one way to define the alias it works, whatever is the function type (property or not) --- import std.traits, std.stdio, std.typetuple; private template MemberType(C, string memberName) { alias member = TypeTuple!(__traits (getMember, C, memberName))[0]; static if (isSomeFunction!(typeof(&member))) alias MemberType = typeof(&member); else static assert(0, "not handled here !"); } struct Hop { void foo(uint a){} property uint bar(){return 0;} uint baz(){return 0;} property void bla(uint h){} } void main() { MemberType!(Hop, "foo").stringof.writeln; MemberType!(Hop, "bar").stringof.writeln; MemberType!(Hop, "baz").stringof.writeln; MemberType!(Hop, "bla").stringof.writeln; } --- 3/ Your example doesn't compile
Oct 28 2015
parent reply Shammah Chancellor <shammah.chancellor gmail.com> writes:
On Thursday, 29 October 2015 at 06:02:05 UTC, qsdf wrote:
 On Thursday, 29 October 2015 at 02:43:59 UTC, Shammah 
 Chancellor wrote:
 ....
I agree with you on an aspect: writing code with __traits() often leads to a cascade of "unfriendly", "cryptic", "undigest", static if and loops. However your examples are discutables: 1/ The symbol is not always a type, so to test it there is `if(is()){}`: --- import std.stdio; enum hack(A...) = A; int main() { foreach(member; __traits(allMembers, std.stdio)) { pragma(msg, member); static if (is(member)) alias sym = hack!(__traits(getMember, std.stdio, member))[0]; } } ---
The point is that you cannot use is() without first using getMember, but using __traits(getMember) before __traits(compiles) generates an error. In your example you're trying to use `is` on a string, which does not work.
 2/ with only one way to define the alias it works, whatever is 
 the function type (property or not)

 ---
 import std.traits, std.stdio, std.typetuple;

 private template MemberType(C, string memberName)
 {
     alias member = TypeTuple!(__traits (getMember, C, 
 memberName))[0];
     static if (isSomeFunction!(typeof(&member)))
         alias MemberType = typeof(&member);
     else static assert(0, "not handled here !");
 }

 struct Hop
 {
     void foo(uint a){}
      property uint bar(){return 0;}
     uint baz(){return 0;}
      property void bla(uint h){}
 }

 void main()
 {
     MemberType!(Hop, "foo").stringof.writeln;
     MemberType!(Hop, "bar").stringof.writeln;
     MemberType!(Hop, "baz").stringof.writeln;
     MemberType!(Hop, "bla").stringof.writeln;
 }
 ---
And you snipped out important sections of code for dealing with fields. The following code now will not compile: getType.d(7): Error: need 'this' for address of bob getType.d(27): Error: template instance getType.MemberType!(Hop, "bob") error instantiating ``` struct Hop { int bob; } void main() { MemberType!(Hop, "bob").stringof.writeln; } ```
 3/ Your example doesn't compile
Sorry, try this: ``` import std.stdio : writeln; import std.typetuple; // This won't work: /+ private template stuff(Args...) { /+ static +/ foreach(Arg; Args) { ... } } +/ // Must do this instead: private template expand(Args...) { static if (Args.length > 0) { static if (Args.length > 1) { enum expand = AliasSeq!(repeat!(Args[0], Args.length), expand!(Args[1..$])); } else static if (Args.length == 1) { enum expand = AliasSeq!(repeat!(Args[0], Args.length)); } } } private template repeat(alias T, int times) { static if ( times > 1 ) { enum repeat = AliasSeq!(T, repeat!(T, times-1)); } else static if (times == 1) { enum repeat = AliasSeq!(T); } } enum things = expand!("A","B","C"); void main() { pragma(msg, things); writeln("Hello World"); } ```
Oct 29 2015
parent qsdf <qsdf qsdf.qsdf.qsdf.fi> writes:
On Thursday, 29 October 2015 at 15:14:13 UTC, Shammah Chancellor 
wrote:
 On Thursday, 29 October 2015 at 06:02:05 UTC, qsdf wrote:
 On Thursday, 29 October 2015 at 02:43:59 UTC, Shammah 
 Chancellor wrote:
 ....
I agree with you on an aspect: writing code with __traits() often leads to a cascade of "unfriendly", "cryptic", "undigest", static if and loops. However your examples are discutables: 1/ The symbol is not always a type, so to test it there is `if(is()){}`: --- import std.stdio; enum hack(A...) = A; int main() { foreach(member; __traits(allMembers, std.stdio)) { pragma(msg, member); static if (is(member)) alias sym = hack!(__traits(getMember, std.stdio, member))[0]; } } ---
The point is that you cannot use is() without first using getMember, but using __traits(getMember) before __traits(compiles) generates an error. In your example you're trying to use `is` on a string, which does not work.
 ```
Right. I forgot to put the pragma message into the compile-time branch and I've been cheated by all the member list printed in the console...Gotta sleep before writing more craps.
Oct 29 2015