I give up! I tried to create a reflection library but D's traits are just too screwed up!
Alex
AJ at gmail.com
Thu Apr 4 02:19:48 UTC 2019
On Wednesday, 3 April 2019 at 17:56:08 UTC, Andrei Alexandrescu
wrote:
> On 4/3/19 3:22 AM, Alex wrote:
>> The follow code is a reflection library that unifies all the
>> junk in std.traits and __traits and makes it using reflection
>> as if it were runtime and one can even keep the info around at
>> runtime...
>>
>> But D's type system is too screwed up.
>
> Alex, thank you for this work. After looking at it, I was all
> morning on the phone with my students Eduard Staniloiu and
> Razvan Nitu, discussing in good part how to make inroads into
> the introspection matter.
>
> Introspection is important. It is also, more or less
> incidentally, central to Eduard's doctoral dissertation and
> also important for Razvan's.
>
> They will look through your code trying to identify and look at
> fixes for the problems you encountered. One thing you could
> help with is to break down the problems you found in bite-size
> chunks - a simple example, the expected behavior, and the
> actual behavior.
>
> In some cases it may be something desirable but not a
> showstopper. Example: "D forces code duplication". In some
> other cases there may be no code example, e.g. "I need
> introspection artifact X to do Y and there isn't any."
>
> Please assign all of these issues to Eduard.
>
>
> Thanks!
Thanks for taking this serious enough to take it serious enough!
(meaning looking in to it).
1. One of the problems here is that as I wrote the code and came
up against problems I'd have to try something and if it didn't
work I'd move on to try something else not taking in to account
why it specifically didn't work.
I will try to break it down though in to pieces.
First, the code is relatively straight forward:
Oop is used because I thought it would better unify CT and RT.
The core feature is this:
abstract class cBaseReflection
{
// Declared in reverse order for proper output
sAttributeReflection[] Attributes; // Attributes on the type
cBaseReflection[] Uses; // Known types that use this type
as a field, parameter, return type, etc
string Body; // D does not allow getting a body but this
is included if it ever does
string Protection;
string MangledName;
string ModuleName; // The module name
string FullName; // The fully qualified name
string TypeName; // The type name
string Id; // The id = __traits(identifier,T)
string DType() { return "Unknown"; }
auto Reflect(alias T)()
{
Id = __traits(identifier, T);
static if (__traits(compiles, T.stringof)) TypeName =
T.stringof;
static if (__traits(compiles, moduleName!T)) ModuleName =
moduleName!(T);
static if (__traits(compiles, fullyQualifiedName!T)) FullName =
fullyQualifiedName!(T);
static if (__traits(compiles, mangledName!T)) MangledName =
mangledName!T;
static if (__traits(compiles, __traits(getProtection, T)))
Protection = __traits(getProtection, T);
// Get the attributes for the type
static if (__traits(compiles, __traits(getAttributes, T)))
static foreach(a; __traits(getAttributes, T)) Attributes ~=
sAttributeReflection(to!string(a), typeof(a).stringof);
return this;
}
}
cBaseReflection is the prototype for all the classes. It simply
contains the common type info of all types. All the other classes
are just specializing for the different D types but have the same
structure.
Reflect is a common templated method who's purpose is to fill out
that information using __traits and std.traits. These then wrap
those functions so we don't have to call them by hand, we just
Reflect!T and it will return the class with the data filled out.
For example, a typical problem in D meta programming is to get
the enum members names and values, the following specialization
of cBaseReflection does this:
/*
Encapsulates an Enumeration Reflection
*/
class cEnumReflection : cBaseReflection
{
static struct sValueReflection
{
string Name;
string Value;
sAttributeReflection[] Attributes;
this(string n, string v) { Name = n; Value = v; }
} sValueReflection[] Values;
string BaseType;
override string DType() { return "enum"; }
auto Reflect(alias T)()
{
super.Reflect!T;
//BaseType = TypeOf(T).stringof;
static foreach(e; EnumMembers!T)
{{
Values ~= sValueReflection(to!string(e), e.stringof);
mixin("alias E = __traits(getAttributes,
T."~to!string(e)~");");
static foreach(a; E)
Values[$-1].Attributes ~= sAttributeReflection(to!string(a),
typeof(a).stringof);
}}
return this;
}
}
Normally we would have to manually do the for each in our code,
now we can just Reflect!SomeEnum.Values[k].Value to get the kth
value.
This is much easier than having to remember the specifics of the
`static foreach(e; EnumMembers!T)` because sometimes there are
__traits to use and sometimes their are templates. Sometimes one
has to use typeof and other times TypeOf(becausse of aliasing
issues).
So Reflect! Abstracts all those issues away(ideally).
Reflect!T calls the parent base Reflect to fill in the more
general info: super.Reflect!T;
The idea is simple, to reduce duplicate code. There should be no
reason to have to duplicate code to get the same info. Since
cBaseReflection.Reflect gets all the general info(and we can use
__traits(compiles,) to handle any minor discrepancies, let
cBaseReflection.Reflect do it's job for all specialized types and
it puts the code in one singular place.
All this basically works without a hitch(although I have
duplicated getting the attributes for the enum. We could possibly
let cBaseReflection or add a Reflect!T to sAttributeReflection to
handle it.
Looking at cAggregateReflection, we have
/*
Encapsulates an Aggregate Type
*/
abstract class cAggregateReflection : cBaseReflection
{
cMethodReflection[] Methods; // The methods
cFieldReflection[] Fields; // The fields
cClassReflection[] DerivedClasses; // Known immedate
derived class that derive this type
cAggregateReflection[] NestedAggregates; // Any nested
aggregates
sTypeReflection[] AliasThis; // The Alias This
sTypeReflection[] TypeParameters; // Any type parameters
used.
bool HasAliasing;
bool HasElaborateAssign;
bool HasElaborateCopyConstructor;
bool HasElaborateDestructor;
bool HasIndirections;
bool HasNested;
bool HasUnsharedAliasing;
bool IsInnerClass;
bool IsNested;
override string DType() { return "Unknown"; }
auto Reflect(alias T)()
{
super.Reflect!T;
// Get the Alias This
static foreach(a; __traits(getAliasThis, T))
{{
mixin("import "~moduleName!T~";");
mixin("AliasThis ~= sTypeReflection(to!string(a),
typeof("~moduleName!T~"."~T.stringof~"."~to!string(a)~").stringof);");
}}
HasAliasing = hasAliasing!T;
HasElaborateAssign = hasElaborateAssign!T;
HasElaborateCopyConstructor = hasElaborateCopyConstructor!T;
HasElaborateDestructor = hasElaborateDestructor!T;
HasIndirections = hasIndirections!T;
HasNested = hasNested!T;
HasUnsharedAliasing = hasUnsharedAliasing!T;
static if (__traits(compiles, isInnerClass!T)) IsInnerClass =
isInnerClass!T;
static if (__traits(compiles, isNested!T)) IsNested =
isNested!T;
// Get the fields
alias n = FieldNameTuple!T;
static foreach(k, f; std.traits.Fields!T)
{{
Fields ~= (new cFieldReflection()).Reflect!(Alias!T, n[k]);
}}
static foreach(k, m; __traits(derivedMembers, T))
{{
static if (__traits(compiles, __traits(getVirtualMethods, T,
m)))
static foreach(j, v; typeof(__traits(getVirtualMethods, T,
m)))
{{
//pragma(msg, T.stringof, " --- ", v.stringof);
Methods ~= (new cMethodReflection()).Reflect!(T, m);
}}
}}
return this;
}
}
and one can see it's pretty straight forward: It's just wrapping
all the basic aggreigate std.trait calls so we don't have to call
them individually.
HasAliasing = hasAliasing!T;
HasElaborateAssign = hasElaborateAssign!T;
HasElaborateCopyConstructor = hasElaborateCopyConstructor!T;
HasElaborateDestructor = hasElaborateDestructor!T;
HasIndirections = hasIndirections!T;
HasNested = hasNested!T;
HasUnsharedAliasing = hasUnsharedAliasing!T;
It obviously calls the super.Reflect! so to avoid duplicating
code.
An aggregate may not have methods or certain other entries but we
include them in this class to be able to handle them more
generally as to avoid duplicate code. Because we have
__traits(compiles,) we can always ignore the cases that will fail
very easily.
The code is quite simple. It's just really basic oop and wrapping
a lot of the traits code, again, to abstract them away.
The general model is
class Child : Parent
{
data
auto Reflect(T)() { super.Reflect!T; ..fill out data.. return
this; }
}
And calling child.Reflect will cascade all the calls and have
each class in the hierarchy fill out the appropriate information
for the type it is acting on(ideally there will be a
corresponding D type(class, field, enum, interface, template,
etc)).
If none of this helps in understanding the code you can reach me
at Incipient at email.com and I'll try to explain it better.
The code was in it's initial stages as it was more prototyping
and seeing what could be done. Ideally one would be able to get
any piece of CT information one desires from Reflect!. I didn't
get finished filling out all the details before I ran in to the
issues below.
-------------------------------------------------
-------------------------------------------------
Now, when we get to filling out field info the issues start to
occur. An aggregate may have fields and so we want to get the
meta info for them:
// Get the fields
alias n = FieldNameTuple!T;
static foreach(k, f; std.traits.Fields!T)
{{
Fields ~= (new cFieldReflection()).Reflect!(Alias!T, n[k]);
}}
To get information about fields one has to use FieldNameTyple to
get the string names of the Id's for the fields and
std.traits.Fields!T to get the types.
//std.traits.Fields
template Fields(T)
{
static if (is(T == struct) || is(T == union))
alias Fields = typeof(T.tupleof[0 .. $ - isNested!T]);
else static if (is(T == class))
alias Fields = typeof(T.tupleof);
else
alias Fields = AliasSeq!T;
}
The problem here is that there is no Field Type in D. That is, a
field is not a type in and of itself. A class is a type. So we
have to pass the type and name that is returned, rather than a
type which would encapsulate that info(and which could then be
gotten inside cFieldsReflection and this would help encapsulate
code)
From the help:
import std.meta : AliasSeq;
struct S { int x; float y; }
static assert(is(Fields!S == AliasSeq!(int, float)));
So, if we want cFieldsReflection to do all the work we have to
then not just parse the field type passed(since we can't) but
pass the info that lets us build the field type.
This alone breaks the design of the code. Specifics are having to
be handled in more general code, which reduces isolation:
E.g, ideally we could do this:
// Get the fields TYPES
static foreach(k, f; std.traits.FieldsTYPE!T)
{{
Fields ~= (new cFieldReflection()).Reflect!(f);
}}
// TYPE for emphasis rather than Type
and then f, as a type(a type in the sense of a self contained
compiler meta unit that can be passed around as a type. It may
not make sense that it is a true D type but for meta programming
it acts like one. E.g. hypothetically we could do
class X { int somefield; }
class Y { typeof(FieldsTYPE!(X)[0]) anotherintfield; }
).
So, instead we have to pass the best info we can that will let us
hopefully get at the info we need to fill out the field data.
Fields ~= (new cFieldReflection()).Reflect!(Alias!T, n[k]);
Here T is passed(the Alias is left over from trying various work
arounds), which is the class the field is in, and n[k] is the
name of the kth field. The idea is that we can build `T.(n[k])`
such as someclass.somefield.
So this gets passed to
/*
Encapsulates a Field Reflection
*/
class cFieldReflection : cBaseReflection
{
override string DType() { return "field"; }
auto Reflect(alias T, string name)()
{
Id = name;
static if (__traits(compiles, TypeName = T.stringof)) TypeName
= T.stringof~"."~name;
ModuleName = moduleName!(T);
FullName = fullyQualifiedName!(T)~"."~name;
MangledName = mangledName!T;
//mixin(`FullName = mangledName!(`~T.stringof~`.`~name~`);`);
// Get the attributes for the field
import mModel;
static if (__traits(compiles, mixin(`static if
(__traits(compiles, __traits(getAttributes,
`~T.stringof~`.`~name~`)))`)))
mixin(`static if (__traits(compiles, __traits(getAttributes,
`~T.stringof~`.`~name~`))) pragma(msg, "fdadsf"); `);
//static if (aFound) { mixin(`static foreach(a;
__traits(getAttributes, `~T.stringof~`.`~name~`)) Attributes ~=
sAttributeReflection(to!string(a), typeof(a).stringof);`);}
//pragma(msg, T);
//T t;
//mixin("alias X = t."~name~";");
//super.Reflect!X;
return this;
}
}
Note that cBaseReflection.Reflect code is duplicated rather than
just calling super.Reflect. Why?
Because we have no type to pass, and Reflect takes a type and
extracts the info. We have to build the info manually using hacks
such as:
Id = name; // easy because the name is the id
TypeName = T.stringof~"."~name; // this just uses
someclass.somefield
The rest just use T, the class or uses someclass.somefield and
hopes for the best
ModuleName = moduleName!(T);
FullName = fullyQualifiedName!(T)~"."~name;
MangledName = mangledName!T;
//mixin(`FullName = mangledName!(`~T.stringof~`.`~name~`);`); //
leftover from testing, was just using fullname for holding the
string
For example, their is no mangling for the field(should their be,
ultimately?). We just use the mangled name of the class, should
work good enough but it is not general.
Now we have to get the attributes, again, cBaseReflection does
all that but we can't call it because Reflect requires a type and
cBaseReflection shouldn't try to handle fields using field names
and the class.
// Get the attributes for the field
import mModel; // We have to import the model because we have no
type that encapsulates it all.
static if (__traits(compiles, mixin(`static if
(__traits(compiles, __traits(getAttributes,
`~T.stringof~`.`~name~`)))`)))
mixin(`static if (__traits(compiles, __traits(getAttributes,
`~T.stringof~`.`~name~`))) pragma(msg, "fdadsf"); `);
//static if (aFound) { mixin(`static foreach(a;
__traits(getAttributes, `~T.stringof~`.`~name~`)) Attributes ~=
sAttributeReflection(to!string(a), typeof(a).stringof);`);}
Note that all this code is simply trying to get the attributes.
Notice how easy it is for a the base type:
// Get the attributes for the type
static if (__traits(compiles, __traits(getAttributes, T)))
static foreach(a; __traits(getAttributes, T)) Attributes ~=
sAttributeReflection(to!string(a), typeof(a).stringof);
But because there is no `T` for fields we can't do it so easy, so
we have to construct the name and use string mixins. But things
start to break down here. One would think that we could just do
// Get the attributes for the field
import mModel;
mixin(`static foreach(a; __traits(getAttributes,
`~T.stringof~`.`~name~`)) Attributes ~=
sAttributeReflection(to!string(a), typeof(a).stringof);`);
which is just the same code except we have to import the model
because now we are using it directly(not as a type but as
someclass.somefield).
This code then breaks down for private fields!
mReflect.d-mixin-201(201): Deprecation:
`mModel.cDerived!int.cDerived.testField2` is not visible from
module `mReflect`
Why? because we are trying to access it directly and it is
private, although it is at compile time, we are in CTFE so the
protection scheme prevents us from accessing.
for example, this code works:
mixin(`import `~moduleName!T~`;`);
static if (name != "")
mixin(`static foreach(a; __traits(getAttributes,
`~T.stringof~`.`~name~`)) Attributes ~=
sAttributeReflection(to!string(a), typeof(a).stringof);`);
Notice how much more "complicated"(rather, messy and error prone)
it is... just because we have to construct the field.
Also that it fails for private fields, it works here because it's
only depreciated, but later on for methods we get an error
instead!
Fields are relatively simple because they are just a name,
attributes, and a type.
Here are the issues though:
Because their is no Field Type:
1. We have to hack things together using string mixins.
2. It requires importing the module that contains the type just
to reflect on it. This then creates protection issues and
possible name collisions(although we can alias the module)
3. We have to pass around the parent type and field in to
templates and construct the fields by hand. This makes then makes
a non-unified system. Notice how much easier it is to deal with
when we just have a field type. It's not just the 2 line of code
but also how it integrates with the rest of the code and the
cBaseReflection. (and all the other templates such as mangledName
and such)
Solution: Seems that adding a Field Type to D would completely
solve all these issues. It is just another type who's main
purpose is to encapsulate field as types making them "1st class".
----------
Now, moving on to methods, essentially the same problem. Because
methods(and functions) are not types in and of themselves, it
requires hacks like the above, but much worse since they are more
complicated.
Some thing here, we gotta import the module that has the method,
meaning same issues as above
mixin(`import `~moduleName!T~`;`); // Must import the type to be
able to inspect it(fragile and bizzare and ridiculous)
T t = T.init; // left over from trying to get things to work
Then, again, we can't call super.Reflect!T to do the work and
most duplicate code:
Id = name;
static if (__traits(compiles, TypeName = T.stringof)) TypeName =
T.stringof~"."~name;
ModuleName = moduleName!(T);
FullName = fullyQualifiedName!(T)~"."~name;
MangledName = mangledName!T;
//mixin(`MangledName = mangledName!(`~T.stringof~`).`~name~`;`);
After messing with this stuff for a few days or so and it just
getting becoming more obvious that it wasn't going to work well I
decided to give up. While it may be possible to hack most things
and get them to work.
For example,
mReflect.d-mixin-222(222): Error: no property `ValueProp` for
type `string`
Of course, it is giving fields, so I have to modify the
cAggregiateReflection code to only return appropriate methods:
static foreach(k, m; __traits(derivedMembers, T))
{{
static if (__traits(compiles, __traits(getVirtualMethods, T,
m)))
static foreach(j, v; typeof(__traits(getVirtualMethods, T,
m)))
{{
//pragma(msg, T.stringof, " --- ", v.stringof);
Methods ~= (new cMethodReflection()).Reflect!(T, m);
}}
}}
so it suggests we need a derivedMembers. I tried to use
getVirtualMethods exclude fields but I guess it didn't work
right, I see that I left the m in their instead of changing it to
a v but v is the type of the method and not a name like m is.
So, the whole point of the Reflect library is to provide a
uniform basis to get the desired information without all this
nonsense(IMO).
It's not that it isn't necessarily doable but one can see that it
requires a lot of unpleasant coding and really destroys the
elegance of using oop and CTFE to solve the problem. If I have to
duplicate the code in each class and use string mixins then what
really is the point? (one could use a single template and string
mixins to solve that but it becomes such a mess that it isn't fun
and makes me wonder what other kinda nonsense will I run in to in
the future?)
My suggestions:
Every D concept should have D type. attributes are types, unions,
classes, fields, methods, enum members, symbols, expressions,
code bodies, etc. This is akin to Object in oop.
Every type template should then work find with all these types
then. Want the module name of an attribute, just do moduleName!a
where a is an attribute type returned by getAttributes.
This may not be feasible for all the things in D at least fields
and methods and whatever other major ones that would be needed.
(templates? I think they are covered under methods?)
Maybe the idea would be better served to be able to box up info
to make a type such as DType and then be able to pass that
around. In that case I could build the field and method types and
then simply use Reflect and all the other easy code to get it to
work.
Ideally the compiler itself would expose all the info available
to a D program in a unified way. e.g., the Reflect library here
would essentially be integrated(conceptually, not the code) in to
the d compiler. This would replace the need for traits.
D could have a reflect keyword:
reflect!T
and one gets back a reflection type that contains all the info
one wants in hierarchal form:
reflect!MyClass.Name
reflect!MyClass.Module.RootTypes
reflect!MyClass.Module.Imports
reflect!MyEnum.Uses // Lets one get all the known uses of
the enum
reflect!MyFunc.Body // returns the code body(we can do this
now by
importing the module as text and locating it but it is fragile)
reflect!MyFunc.Location // returns the Source location(file,
line, etc)
reflect!MyAttribute.New(Name = MyOtherAttribute, Value = 434) //
Lets one construct a new type based off the old one, maybe not a
great idea)
reflect!MyClass.New(Name = Name~Other, Body = { })
etc...
The goal of the code I wrote was to provide as much info as I can
get in a unified way(as I've said many times already). Since it
just wraps traits there is no drawback that I can think of and
there shouldn't be(although since it uses classes it might not be
usable in all areas of D).
If those in the know got serious about this idea it could provide
a very powerful and elegant solution for D's meta system that
will heavily complement it(it would also be easy to extend since
one just ads to the hierarchy and all the internals are
abstracted away).
I use meta programming heavily in D and that is the main thing I
like about it. Being able to write generic code means being able
to work in general terms. This then requires the ability to
introspect in the type system. D has the abilities but the
conceptualization is very messy. For one, there is __traits and
then there is std.traits. One could argue that one can wrap
everything in __traits but that is easier said than done(as I
have proven).
One problem with a "library" solution is speed. The compiler
already parses all this information once and by having a internal
reflect! the data can be filled out once during compilation and
the using it is just looking up that data(at the cost of memory,
could be lazy though).
I'm not sure the cost of repeatedly calling traits functions, if
they are essentially memorized or if it requires some
calculation, but if it does require any significant time, I
imagine that a reflect! would then drastically increase
compilation times when heavy meta programming is involved) as
most introspection's would just be a look up.
Anyways, hopefully you guys can come up with something usable. I
find that D's meta programming is very capable but also can be
very tiresome to do simple things. I find myself having to use
string mixins quite often which makes it very difficult to debug
and read but also seems extremely unnecessary. The same patterns
are repeated over and over and over.
While much of this work may be long term(D3?)[although, to be
honest, it didn't take me that long to through the code I wrote
together. Most is just wrapping. It's the anomalies of traits
that caused the problem] I'd hope that at the very least a good
solution to the main problems of fields and methods/functions in
this library could be created.
Thanks.
More information about the Digitalmars-d
mailing list