www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - qualified type names for mixins

reply Jonathan Marler <johnnymarler gmail.com> writes:
I've found that the fullyQualifiedName template in std.traits is 
a good tool for creating mixin code, however, it doesn't always 
work.

-----
import std.traits;

struct GlobalFoo
{
     int x;
}

// WORKS
mixin(fullyQualifiedName!GlobalFoo ~ " globalFoo;");

unittest
{
     static struct Foo
     {
         int x;
     }
     // Error: no property 'Foo' for type 'void'
     mixin(fullyQualifiedName!Foo ~ " foo;");
}

void main()
{
     static struct Foo
     {
         int x;
     }
     // Error: no property 'Foo' for type 'void'
     mixin(fullyQualifiedName!Foo ~ " foo;"); // Error: no 
property 'Foo' for type 'void'
}
-----

The problem in the example is that you can't access the Foo 
struct outside the unittest/main function, even if you have a 
qualified name.  I'm wondering if adding support for cases like 
this should be considered?  Should you be able to access types 
defined inside a unittests/functions?

Another idea to help with this would be to add a new template to 
phobos, relativeQualifiedName.  This would take the current 
context into account and remove that from the fullyQualifiedName.

module foo;

struct baz
{
      void bar()
      {
          struct bon
          {
          }
          assert(fullyQualifiedName!bon == "foo.baz.bar.bon");
          assert(relativeQualifiedName!bon == "bon");
      }
}

I've thought about how to implement this but haven't figured out 
a good way.  I had something like the following in mind:

template relativeQualifiedName(T)
{
     string relativeQualifiedName(string context = 
__QUALIFIED_NAME_CONTEXT__)
     {
         return fullyQualifiedName!T[context.length+1..$];
     }
}

Of course, __QUALIFIED_NAME_CONTEXT__does not exist in the 
langauge so this wouldn't work. Any thoughts/suggestions?  Can 
someone think of a way to implement relativeQualifiedName?
Jun 15
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 15 June 2017 at 19:15:55 UTC, Jonathan Marler wrote:
 I've found that the fullyQualifiedName template in std.traits 
 is a good tool for creating mixin code, however, it doesn't 
 always work.
Why is it useful? I suggest you are probably doing it wrong. https://stackoverflow.com/questions/32615733/struct-composition-with-mixin-and-templates/32621854#32621854 always use local names in string mixins. Don't try to build strings with other names, just use the one you already have in scope.
Jun 15
parent reply Jonathan Marler <johnnymarler gmail.com> writes:
On Thursday, 15 June 2017 at 20:34:39 UTC, Adam D. Ruppe wrote:
 On Thursday, 15 June 2017 at 19:15:55 UTC, Jonathan Marler 
 wrote:
 I've found that the fullyQualifiedName template in std.traits 
 is a good tool for creating mixin code, however, it doesn't 
 always work.
Why is it useful? I suggest you are probably doing it wrong. https://stackoverflow.com/questions/32615733/struct-composition-with-mixin-and-templates/32621854#32621854 always use local names in string mixins. Don't try to build strings with other names, just use the one you already have in scope.
This example is contrived to demonstrate the usage, obviously you would never need to use fullyQualifiedName!T if you can just use the type name directly. The common use case is when you'd like to mixin a type when it is passed to a template. For example, I recently made an addition to the bitfields function that adds support for other bool-like types like std.traits.Flag. The bitfields template takes the type as an alias parameter and then mixin's in the name of the type, however, there's no general way to mixin the type from an alias. It uses T.stringof in some cases, and I had to do some hacky template stuff to make std.traits.Flag work. fullyQualifiedName also doesn't work because of the issues explained here.
Jun 15
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 15 June 2017 at 21:26:38 UTC, Jonathan Marler wrote:
 The common use case is when you'd like to mixin a type when it 
 is passed to a template.
That's also the most common wrong case. If it is passed to a template, you have a local name that you should use because it will work despite import or privacy restrictions that would break with the full name. So....
 It uses T.stringof in some cases
...just use T. Drop the stringof and you'll find it much easier and correct.
Jun 15
parent reply Jonathan Marler <johnnymarler gmail.com> writes:
On Thursday, 15 June 2017 at 21:54:44 UTC, Adam D. Ruppe wrote:
 On Thursday, 15 June 2017 at 21:26:38 UTC, Jonathan Marler 
 wrote:
 The common use case is when you'd like to mixin a type when it 
 is passed to a template.
That's also the most common wrong case. If it is passed to a template, you have a local name that you should use because it will work despite import or privacy restrictions that would break with the full name. So....
Doesn't work with eponymous templates, like std.traits.Flag. For example, make this code work: import std.stdio, std.traits; template TypeWrapper(T) { alias TypeWrapper = T; } void main() { writefln(TypeWrapper!bool.stringof); // Works writefln(TypeWrapper!(Flag!"yes").stringof); // Doesn't work }
 It uses T.stringof in some cases
...just use T. Drop the stringof and you'll find it much easier and correct.
When you mixin the type, you need a "string', T is not a string, it's an alias to a type.
Jun 15
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 15 June 2017 at 22:36:56 UTC, Jonathan Marler wrote:
 Doesn't work with eponymous templates, like std.traits.Flag.  
 For example, make this code work:
That uses `.stringof` which means it is useless for anything except informational printing. Post the code you are actually doing (not writeln stuff, with mixin) and I'll show you how to do it right.
 When you mixin the type, you need a "string', T is not a 
 string, it's an alias to a type.
But "T" is a string....
Jun 15
parent reply Jonathan Marler <johnnymarler gmail.com> writes:
On Thursday, 15 June 2017 at 22:59:23 UTC, Adam D. Ruppe wrote:
 On Thursday, 15 June 2017 at 22:36:56 UTC, Jonathan Marler 
 wrote:
 Doesn't work with eponymous templates, like std.traits.Flag.  
 For example, make this code work:
That uses `.stringof` which means it is useless for anything except informational printing. Post the code you are actually doing (not writeln stuff, with mixin) and I'll show you how to do it right.
Like I said the example I'm working with is my PR for the bitfields function: https://github.com/dlang/phobos/pull/5441/files The bitfields function takes an alias to type for each "bit field", and then returns a string that can be passed to mixin. As you can see, it uses T.stringof, so to get the std.traits.Flag enum to work I had to add some "hacky template voodoo". wilzbach commented on a particular template on line 76: private template TypeName(T) { static if (__traits(compiles, TemplateOf!T)) { enum TypeName = T.stringof ~ "!" ~ TemplateArgsOf!T.stringof[5..$]; } else { enum TypeName = T.stringof; } } He said he was surprised a version of this wasn't already in phobos and I responded saying it doesn't exist AFAIK, likely because it's very hard or not possible with currently language features. My hacky attempt will only work with basic template types and isn't near a solution that works in the general case. However I think it could be implemented with relativeQualifiedName!T (as shown in my first post of this thread) or possibly by modifying some things in the language to make fullyQualfiedName!T work in more cases. If you have a better idea on how to implement the bitfields template that would be great. So far it seems you feel that this isn't a problem because any code that runs in to this problem must be "wrong" in the first place. Maybe you're right, I would love to see a better solution, but I haven't thought of one. Thanks.
Jun 15
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Friday, 16 June 2017 at 03:26:28 UTC, Jonathan Marler wrote:
 If you have a better idea on how to implement the bitfields 
 template that would be great.
The real WTF is that it returns a string in the first place. It should return a struct. Here, take a look at this: ------------- /++ Type, "name", 4 /* size_in_bits */ // repeat +/ mixin template bitfields(T...) { mixin((function() { import std.format; string code; code = "struct {"; string getName(size_t idx)() { return T[idx]; } foreach(i, t; T) { static if(i % 3 == 0) { // I'm just doing a fake getter here to demo the // technique, you can do setter the same way code ~= format("T[%d] %s() { return cast(typeof(return)) T[%d]; }\n", i + 0, T[i + 1], i + 2); } } code ~= "}"; return code; })()); } import std.typecons; struct HasBitfields { mixin bitfields!( bool, "bool_thing", 1, int, "int_thing", 4, Flag!"CustomFlag", "custom_flag", 1, ); } void main() { HasBitfields bf; assert(bf.bool_thing == 1); assert(bf.int_thing == 4); import std.stdio; foreach(n; __traits(allMembers, HasBitfields)) // this is the only time you should ever use stringof - printing // basic info to the user that is not meant to be seriously parsed; // it is a debugging aid. writeln(typeof(__traits(getMember, HasBitfields, n)).stringof, " ", n); } -------------- I didn't actually implement the getter/setter functions correctly, but adapting the current code to use this superior technique shouldn't be too hard. Of course, it isn't 100% compatible on the user side... but, as you can see in the example, all types just work with no name troubles. Notice this code here: code ~= format("T[%d] %s() { return cast(typeof(return)) T[%d]; }\n", i + 0, T[i + 1], i + 2); There's no `.stringof` in there. Instead, I just use `T[x]` to represent the type - the local alias that was passed in by the user.
Jun 15
next sibling parent Jonathan Marler <johnnymarler gmail.com> writes:
On Friday, 16 June 2017 at 03:57:17 UTC, Adam D. Ruppe wrote:
 On Friday, 16 June 2017 at 03:26:28 UTC, Jonathan Marler wrote:
 If you have a better idea on how to implement the bitfields 
 template that would be great.
The real WTF is that it returns a string in the first place. It should return a struct. Here, take a look at this: ------------- /++ Type, "name", 4 /* size_in_bits */ // repeat +/ mixin template bitfields(T...) { mixin((function() { import std.format; string code; code = "struct {"; string getName(size_t idx)() { return T[idx]; } foreach(i, t; T) { static if(i % 3 == 0) { // I'm just doing a fake getter here to demo the // technique, you can do setter the same way code ~= format("T[%d] %s() { return cast(typeof(return)) T[%d]; }\n", i + 0, T[i + 1], i + 2); } } code ~= "}"; return code; })()); } import std.typecons; struct HasBitfields { mixin bitfields!( bool, "bool_thing", 1, int, "int_thing", 4, Flag!"CustomFlag", "custom_flag", 1, ); } void main() { HasBitfields bf; assert(bf.bool_thing == 1); assert(bf.int_thing == 4); import std.stdio; foreach(n; __traits(allMembers, HasBitfields)) // this is the only time you should ever use stringof - printing // basic info to the user that is not meant to be seriously parsed; // it is a debugging aid. writeln(typeof(__traits(getMember, HasBitfields, n)).stringof, " ", n); } -------------- I didn't actually implement the getter/setter functions correctly, but adapting the current code to use this superior technique shouldn't be too hard. Of course, it isn't 100% compatible on the user side... but, as you can see in the example, all types just work with no name troubles. Notice this code here: code ~= format("T[%d] %s() { return cast(typeof(return)) T[%d]; }\n", i + 0, T[i + 1], i + 2); There's no `.stringof` in there. Instead, I just use `T[x]` to represent the type - the local alias that was passed in by the user.
This has potential. I'll work with this to see if I can implement a better version of bitfields. Since it is called slightly differently phobos inclusion is more complex, requiring a deprecation process or a different name, however, even if it's not in phobos I can use it in my own projects which is utlimately what I need. I do like this API better since a "mixin template" more clearly indicates what bifields is doing, mixing in some fields. I see you put a comment in your example, // this is the only time you should ever use stringof - printing // basic info to the user that is not meant to be seriously parsed; // it is a debugging aid. Obviously T.stringof falls very short when used for mixing in type names. However the real question I posed was how to implement a function that can be used for this. I showed how fullyQualifiedName doesn't work either, but I think an implementation of relativeQualifiedName would work. I see your argument that such a template shouldn't be necessary, but that assumes that there is no useful case for mixing in types names. I'm not convinced that this is the case but I haven't given you a counter-example so I'll leave it for now. In the meantime thanks for a potential solution to the bitfields problem.
Jun 15
prev sibling parent Jonathan Marler <johnnymarler gmail.com> writes:
On Friday, 16 June 2017 at 03:57:17 UTC, Adam D. Ruppe wrote:
 On Friday, 16 June 2017 at 03:26:28 UTC, Jonathan Marler wrote:
 [...]
The real WTF is that it returns a string in the first place. It should return a struct. [...]
PR Here: https://github.com/dlang/phobos/pull/5490 Currently not commented. Improvements/suggestions welcome.
Jun 16