www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - the traits trap

reply Steven Schveighoffer <schveiguy yahoo.com> writes:
OK, so I'm writing some traits that I'd like my objects to satisfy. And 
I'm having the worst time debugging them.

Most of the traits in D look like this:

enum isSomeType(T) = __traits(compiles, (T t){
    // some statements using t
    // some asserts
    // some static asserts
});

All good.

Now, let's test my object:

unittest
{
    static assert(isSomeType!SomeObject);
}

Nope. Now, how the hell do I figure out why?

I have found the following technique most valuable:

1. Create a function called "testSomeType(T)(T t)", make it's body the 
same as the trait
2. Instead of static asserting the trait, call the function

Much better results! Whichever part of the trait doesn't work shows up 
as a legitimate error, and I can fix the object or the trait.

Now, this idiom of using __traits(compiles, ...) is used everywhere in 
phobos. Often times you see things like:

void foo(T)(T t) if (hasSomeTrait!T && hasSomeOtherTrait!T && 
alsoHasThisOne!T) { ...

If this doesn't compile, the compiler says "Error template instance blah 
blah does not match template declaration blah blah blah"

Useless...

Now, even if I want to use my cool technique to figure out where the 
issue is, I have to do it one at a time to each trait, and I may have to 
temporarily comment out some code to avoid triggering an error before I 
get to that point.

When I first came to write this post, I wanted to ask if anyone thought 
it was a good idea to replace the __traits(compiles, someLiteral) with 
__traits(compiles, someFunctionTemplate!T) somehow, so if one couldn't 
do it, you had some easy way to debug by calling someFunctionTemplate.

But I hate that idea. This means you have all these do-nothing functions 
whose sole existence is to debug traits. When the traits themselves can 
just do it for you.

Can anyone figure out a good solution to this problem? I like template 
constraints, but they are just too black-boxy. Would we have to signify 
that some enum is actually a trait and so the compiler would know to 
spit out the junk of compiling? Would it make sense to add some __traits 
function that allows one to signify that this is a special trait thing?

This is one area that D's templates are very user-unfriendly.

-Steve
Nov 20 2014
next sibling parent "Meta" <jared771 gmail.com> writes:
On Friday, 21 November 2014 at 04:08:52 UTC, Steven Schveighoffer 
wrote:
 OK, so I'm writing some traits that I'd like my objects to 
 satisfy. And I'm having the worst time debugging them.

 Most of the traits in D look like this:

 enum isSomeType(T) = __traits(compiles, (T t){
    // some statements using t
    // some asserts
    // some static asserts
 });

 All good.

 Now, let's test my object:

 unittest
 {
    static assert(isSomeType!SomeObject);
 }

 Nope. Now, how the hell do I figure out why?

 I have found the following technique most valuable:

 1. Create a function called "testSomeType(T)(T t)", make it's 
 body the same as the trait
 2. Instead of static asserting the trait, call the function

 Much better results! Whichever part of the trait doesn't work 
 shows up as a legitimate error, and I can fix the object or the 
 trait.

 Now, this idiom of using __traits(compiles, ...) is used 
 everywhere in phobos. Often times you see things like:

 void foo(T)(T t) if (hasSomeTrait!T && hasSomeOtherTrait!T && 
 alsoHasThisOne!T) { ...

 If this doesn't compile, the compiler says "Error template 
 instance blah blah does not match template declaration blah 
 blah blah"

 Useless...

 Now, even if I want to use my cool technique to figure out 
 where the issue is, I have to do it one at a time to each 
 trait, and I may have to temporarily comment out some code to 
 avoid triggering an error before I get to that point.

 When I first came to write this post, I wanted to ask if anyone 
 thought it was a good idea to replace the __traits(compiles, 
 someLiteral) with __traits(compiles, someFunctionTemplate!T) 
 somehow, so if one couldn't do it, you had some easy way to 
 debug by calling someFunctionTemplate.

 But I hate that idea. This means you have all these do-nothing 
 functions whose sole existence is to debug traits. When the 
 traits themselves can just do it for you.

 Can anyone figure out a good solution to this problem? I like 
 template constraints, but they are just too black-boxy. Would 
 we have to signify that some enum is actually a trait and so 
 the compiler would know to spit out the junk of compiling? 
 Would it make sense to add some __traits function that allows 
 one to signify that this is a special trait thing?

 This is one area that D's templates are very user-unfriendly.

 -Steve
There has been a bit of promising work done by Shammah Chancellor. It's a bit more heavyweight than a template returning true or false, but it's also more powerful and makes for better error messages. http://forum.dlang.org/thread/m219bj$fpa$1 digitalmars.com
Nov 20 2014
prev sibling next sibling parent reply "Sergei Nosov" <sergei.nosov gmail.com> writes:
On Friday, 21 November 2014 at 04:08:52 UTC, Steven Schveighoffer 
wrote:
 Can anyone figure out a good solution to this problem? I like 
 template constraints, but they are just too black-boxy. Would 
 we have to signify that some enum is actually a trait and so 
 the compiler would know to spit out the junk of compiling? 
 Would it make sense to add some __traits function that allows 
 one to signify that this is a special trait thing?

 This is one area that D's templates are very user-unfriendly.

 -Steve
I would second this. Personally, I have the same "not very pleasant" experience debugging template constraints. Since more often than not the constraints have the form of: if (clause1 && clause2 && clause3 ...) my naive proposal would be to show which clause was first to be false in the error message. However, I have no idea if this could be implemented easily.
Nov 20 2014
parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 11/21/14 2:25 AM, Sergei Nosov wrote:
 On Friday, 21 November 2014 at 04:08:52 UTC, Steven Schveighoffer wrote:
 Can anyone figure out a good solution to this problem? I like template
 constraints, but they are just too black-boxy. Would we have to
 signify that some enum is actually a trait and so the compiler would
 know to spit out the junk of compiling? Would it make sense to add
 some __traits function that allows one to signify that this is a
 special trait thing?

 This is one area that D's templates are very user-unfriendly.

 -Steve
I would second this. Personally, I have the same "not very pleasant" experience debugging template constraints. Since more often than not the constraints have the form of: if (clause1 && clause2 && clause3 ...) my naive proposal would be to show which clause was first to be false in the error message.
That helps, but you still have to figure out why that clause fails.
 However, I have no idea if this could be implemented easily.
I think it's a good idea in general. Inevitably, a template constraint breaks down into a decision tree, and knowing which decisions contributed to the false at the top is essential. Of course, this is only needed if it *doesn't* compile. What I'd like to see is a combination of both determining at the highest level which part of the if expression caused the failure (this is only if the thing doesn't compile), and then add a new feature: static assert(__traits(analyzeTrait, isSomeTrait!x)); The analyzeTrait directive would compile isSomeTrait!x, keeping track of the decision tree (or optionally, re-compiling it to print the tree), and whichever pieces caused it to be 0, including __traits(compiles, ...) error messages, and if the result ends up being non-zero, it just discards that tree. If the result ends up being 0, then the static assert prints the decision tree. An example: enum isFoo(T) = (is(T == int) || is(T == long)) && __traits(compiles, (T t) { blah(t); }); void blah(int x); static assert(__traits(analyzeTrait, isFoo!int)); // => no messages, compilation continues static assert(__traits(analyzeTrait, isFoo!long)); // Compilation stops, output is: Error: analyzeTrait returned false for isFoo!(T) where T == long: is(T == int) => false [1] is(T == long) => true [2] [1] || [2] => true [3] __traits(compiles, ...) => false [4] Error: no overload of blah for long [3] && [4] => false Note, the __traits(compiles, ...) line would show the entire compile expression for reference. Something like this makes analysis of why the static assert failed so much better. You could just build this into static assert, but I think it might be too unwieldy to see all the messages. Sometimes, just knowing static assert fails some involved test is fine, you don't need the details. -Steve
Nov 21 2014
prev sibling parent "Paolo Invernizzi" <paolo.invernizzi no.address> writes:
On Friday, 21 November 2014 at 04:08:52 UTC, Steven Schveighoffer 
wrote:
 This is one area that D's templates are very user-unfriendly.

 -Steve
+1, Well said! --- Paolo
Nov 20 2014