View mode: basic / threaded / horizontal-split · Log in · Help
March 16, 2012
Proposal: user defined attributes
On the ride over here today, I had a thought that
I think neatly solves the user defined attribute
question.

enum Serializable { yes, no }

@note(Serializable.yes) int a;


We introduce two new things to the language:

1) the @note(expression) attribute. You can put
as many "notes" on a declaration as you want by
simply listing them.

The expression inside is appended to a list on
the declaration.

This would be valid on variables, functions,
function parameters, struct members, etc.

2) __traits(getNotes, decl). This returns a tuple
of all the note expressions.

foreach(i, exp; __traits(getNotes, a)) {
   static assert(is(typeof(exp) == Serializable);
   static assert(exp == Serializable.yes);
}




This simple extension to the language enables libraries,
using existing traits to navigate symbols, to get
additional information about things and implement it
however.

The lack of user defined attributes is D's biggest missed
opportunity right now, and I think this simple proposal
will plug that hole.



Previous user-defined attribute proposals have had counter
arguments like these:

1) how do you define what is and is not a valid attribute?

Here, the answer is pretty simple: you can put whatever notes
you want on the thing. Whether the library uses it or not
is up to it.

It has to be a valid type - so the compiler will catch
typos and whatnot - but it doesn't have to be used unless
you ask for it.


2) OK, how do you namespace things so different libraries don't
get different results?

That's where the beauty of the expression type comes in: D
already has namespaces. foo.Serializable != bar.Serializable,
so there's no conflict.

We simply declare types. The enum X {yes, no} pattern is already
common in Phobos, and is also the best way to express a bool 
condition
here. (Technically, @note(true) would compile, but it'd be much 
less
useful than declaring a name for the note too, with an enum.)

Name conflicts is a problem already solved by the language.


3) Can we have shared attributes, with declared names so people
know what to use in their own libraries?

Yes, we could add some to std.traits or make a new std.notes that
declare some common items as enums.

Though, I think having the types declared in the modules that use
them is generally more useful.


But, again, the library problem is already solved, and we're
just using that!





I think this is a winner... and I think I can do it in the
compiler in less time than it will take to convince git to
give me a clean pull request.


Am I missing anything?
March 16, 2012
Re: Proposal: user defined attributes
On 16 March 2012 15:35, Adam D. Ruppe <destructionator@gmail.com> wrote:

> @note(Serializable.yes) int a;
>

Surely the term you're looking for here is @annotate(...) ?

This simple extension to the language enables libraries,
> using existing traits to navigate symbols, to get
> additional information about things and implement it
> however.
>
> The lack of user defined attributes is D's biggest missed
> opportunity right now, and I think this simple proposal
> will plug that hole.
>

I agree it's a very big hole against popular competing languages (Java, C#).

Previous user-defined attribute proposals have had counter
> arguments like these:
>
> 1) how do you define what is and is not a valid attribute?
>
> Here, the answer is pretty simple: you can put whatever notes
> you want on the thing. Whether the library uses it or not
> is up to it.
>

What if you want to annotate with a variable? Each instance may need to
hold some attribute state. This is extremely common, particularly in
serialisation systems which typically perform lazy updates, and need some
additional state for that.


> 2) OK, how do you namespace things so different libraries don't
> get different results?
>

Surely this is just as easy: @modulename.attribute int myThing;


> Am I missing anything?
>

I think it only solves half the problem. There are certainly cases where
you just want to annotate with some sort of tag, so you can know to do
something special in this thing's case, but there are also times where you
want to associate some variable data with each instance.

Perhaps that's the key distinction between 'annotation' and a 'custom
attributes' .. an annotation this way is a shared compile time constant,
associating with its type info, as you suggest. A custom attribute is an
instance-specific association vontaining variable data.
I'd suggest that @annotate(someEnum) could work effectively just as you
suggest, but it doesn't fill the requirement for custom attributes, which
could be implemented separately, something like:

attribute myAttribute
{
 this(int something);

 bool bNeedsAttention;

 property void refresh(bool bRefresh) { bNeedsAttention = bRefresh; }
}

@myAttribute(10) int thing;

thing.refresh = true;
March 16, 2012
Re: Proposal: user defined attributes
On Friday, 16 March 2012 at 14:11:35 UTC, Manu wrote:
> Surely the term you're looking for here is @annotate(...) ?

Meh, I don't care that much about names. I went
with "note" anticipating people would complain about
@add_user_defined_attribute() being too long :)


> What if you want to annotate with a variable?

That *might* work because a variable is an expression too.
I'm not sure though. Will probably have to implement to
know for sure.

Of course, a constant can be represented as a struct
for name and value:

struct Description { string s; }

@note(Description("yada yada yada")) int a;


> Surely this is just as easy: @modulename.attribute int myThing;

Yeah, I think so. I remember this being a counterpoint
last time we talked about it, but I don't recall the
specific argument made.

> Perhaps that's the key distinction between 'annotation' and a 
> 'custom attributes' .. an annotation this way is a shared 
> compile time constant, associating with its type info, as you 
> suggest. A custom attribute is an instance-specific association 
> vontaining variable data.

Yeah, "annotation" might be the better word. That's
what I want here, but too late to change the subject name
now.


> attribute myAttribute

I think this could be done as a struct, but I haven't
used this kind of attribute at all so not sure...

But what I'm thinking is:

struct myAttribute(Type) {
   Type value;
   alias value this;

   bool bNeedsAttention;
   @property void refresh(bool bRefresh) { bNeedsAttention = 
bRefresh; }
}

myAttribute!int thing;
thing.refresh = true;

and thing can be substituted for an int anywhere else.
March 16, 2012
Re: Proposal: user defined attributes
Another argument against would be "can we do
this in the language today?"

And the answer is "sort of, but not really":

void a(@note(...) int arg) {}


You could perhaps do something like:

void a(int arg) {}
mixin("enum " ~ a.mangleof ~ "_arg = " ~ ...);

and then get the listing by looking at allMembers
and comparing the name... but, it gets messy
fast - the attribute is somewhat far from the declaration,
so I betcha it will get out of sync.


Getting the attribute on param 0 too would consist
of using stringof tricks to get the name, then mixing
it in to get that variable.


This isn't a proper tuple either - more care would be
needed to handle multiple notes.



So, you could make it work, but it is hideous, fragile,
and probably less useful even if you look past that.

I've seen some nice implementations of struct level annotations,
using mixin templates, but the killer feature for me, personally,
is putting it on function parameters as well.


I think the language addition (which breaks nothing existing
today; it is purely additive) is necessary to get something
generally useful, not just useful in a few cases.
March 16, 2012
Re: Proposal: user defined attributes
On 16 March 2012 16:51, Adam D. Ruppe <destructionator@gmail.com> wrote:

> On Friday, 16 March 2012 at 14:11:35 UTC, Manu wrote:
>
>> Surely the term you're looking for here is @annotate(...) ?
>>
>
> Meh, I don't care that much about names. I went
> with "note" anticipating people would complain about
> @add_user_defined_attribute() being too long :)
>
>
>
>  What if you want to annotate with a variable?
>>
>
> That *might* work because a variable is an expression too.
> I'm not sure though. Will probably have to implement to
> know for sure.
>
> Of course, a constant can be represented as a struct
> for name and value:
>
> struct Description { string s; }
>
> @note(Description("yada yada yada")) int a;
>
>
>
>  Surely this is just as easy: @modulename.attribute int myThing;
>>
>
> Yeah, I think so. I remember this being a counterpoint
> last time we talked about it, but I don't recall the
> specific argument made.
>
>
>  Perhaps that's the key distinction between 'annotation' and a 'custom
>> attributes' .. an annotation this way is a shared compile time constant,
>> associating with its type info, as you suggest. A custom attribute is an
>> instance-specific association vontaining variable data.
>>
>
> Yeah, "annotation" might be the better word. That's
> what I want here, but too late to change the subject name
> now.
>
>
>  attribute myAttribute
>>
>
> I think this could be done as a struct, but I haven't
> used this kind of attribute at all so not sure...
>
> But what I'm thinking is:
>
> struct myAttribute(Type) {
>   Type value;
>   alias value this;
>
>   bool bNeedsAttention;
>   @property void refresh(bool bRefresh) { bNeedsAttention = bRefresh; }
> }
>
> myAttribute!int thing;
> thing.refresh = true;
>
> and thing can be substituted for an int anywhere else.
>

Interesting approach, how will that affect 'thing's type?
March 16, 2012
Re: Proposal: user defined attributes
On Friday, 16 March 2012 at 15:10:06 UTC, Manu wrote:
> Interesting approach, how will that affect 'thing's type?

It will strictly be myAttribute!int, but alias this
means that you can pass it anywhere an int is expected
too (and assign ints to it all the same).

Check it:

void cool(int a) {}
void main() {
        myAttribute!int thing;
        thing.refresh = true;

        thing = 10; // we can assign ints to it, like if it was 
an int
        assert(thing.bNeedsAttention); // should be unchanged

        int a = thing; // no problem, thing is usable as an int
        assert(a == 10);

        cool(thing); // ditto
}
March 16, 2012
Re: Proposal: user defined attributes
On 16 March 2012 17:14, Adam D. Ruppe <destructionator@gmail.com> wrote:

> On Friday, 16 March 2012 at 15:10:06 UTC, Manu wrote:
>
>> Interesting approach, how will that affect 'thing's type?
>>
>
> It will strictly be myAttribute!int, but alias this
> means that you can pass it anywhere an int is expected
> too (and assign ints to it all the same).
>
> Check it:
>
> void cool(int a) {}
> void main() {
>
>        myAttribute!int thing;
>        thing.refresh = true;
>
>        thing = 10; // we can assign ints to it, like if it was an int
>        assert(thing.bNeedsAttention); // should be unchanged
>
>        int a = thing; // no problem, thing is usable as an int
>        assert(a == 10);
>
>        cool(thing); // ditto
> }
>

I can see that it might work nicely in very simple cases, but it could get
nasty in complex constructs. If some template tries to take the typeof for
instance, now I'm cloning the attributes functionality too, which I don't
think is desirable...
March 16, 2012
Re: Proposal: user defined attributes
Adam D. Ruppe wrote:
> On the ride over here today, I had a thought that
> I think neatly solves the user defined attribute
> question.
>
> enum Serializable { yes, no }
>
> @note(Serializable.yes) int a;

This is just enum. If you use struct or class attributes (like in C#) 
then direct @Struct(constructor args..) might be better.

Moreover, structs and classes may have special member which validates 
attribute target:

struct MyAttribute
{
    template canAttach(alias Symbol)
    {
        // attribute is only valid on structs and
        // may be used only once per symbol
        enum canAttach = is(Symbol == struct) &&
                         !__traits(hasNotes, Symbol);
    }
}

It should be automatically evaluated by the compiler after attribute is 
attached.

Alternatively it may be written like this:

template validate(alias Symbol)
{
    static assert(is(Symbol == struct), "MyAttribute must be used with 
structs");
}

> We introduce two new things to the language:
>
> 1) the @note(expression) attribute. You can put
> as many "notes" on a declaration as you want by
> simply listing them.
>
> The expression inside is appended to a list on
> the declaration.
>
> This would be valid on variables, functions,
> function parameters, struct members, etc.
>
> 2) __traits(getNotes, decl). This returns a tuple
> of all the note expressions.
>
> foreach(i, exp; __traits(getNotes, a)) {
> static assert(is(typeof(exp) == Serializable);
> static assert(exp == Serializable.yes);
> }

This is nice. Also it's base for library functions like hasNotes or 
getNotes!(Symbol, MyAttribute).

> This simple extension to the language enables libraries,
> using existing traits to navigate symbols, to get
> additional information about things and implement it
> however.
>
> The lack of user defined attributes is D's biggest missed
> opportunity right now, and I think this simple proposal
> will plug that hole.

Yes, it's big drawback for serialization and data binding code.

> Previous user-defined attribute proposals have had counter
> arguments like these:
>
> 1) how do you define what is and is not a valid attribute?
>
> Here, the answer is pretty simple: you can put whatever notes
> you want on the thing. Whether the library uses it or not
> is up to it.
>
> It has to be a valid type - so the compiler will catch
> typos and whatnot - but it doesn't have to be used unless
> you ask for it.

It should be any user-defined type that may be used at compile-time. For 
example @UUID("...") should be available out of the box.

> 2) OK, how do you namespace things so different libraries don't
> get different results?
>
> That's where the beauty of the expression type comes in: D
> already has namespaces. foo.Serializable != bar.Serializable,
> so there's no conflict.
>
> We simply declare types. The enum X {yes, no} pattern is already
> common in Phobos, and is also the best way to express a bool condition
> here. (Technically, @note(true) would compile, but it'd be much less
> useful than declaring a name for the note too, with an enum.)
>
> Name conflicts is a problem already solved by the language.

Yes, it should be possible to write @std.UUID("").

> 3) Can we have shared attributes, with declared names so people
> know what to use in their own libraries?

Of course, for example consider a validating attribute "Flags":

@Flags
enum SomeFlags
{
    a = 1,
    b = 2,
    c = 4,
    d = 8
}

During attachment it would check if enum member's bits don't overlap.
March 16, 2012
Re: Proposal: user defined attributes
On 3/16/12 8:35 AM, Adam D. Ruppe wrote:
> enum Serializable { yes, no }
>
> @note(Serializable.yes) int a;
[...]
> foreach(i, exp; __traits(getNotes, a)) {
> static assert(is(typeof(exp) == Serializable);
> static assert(exp == Serializable.yes);
> }

So we have:

class A {
    @note(Serializable.yes) int a;
    ...
}

vs. a hypothetical in-language solution:

class A {
    int a;
    mixin(note("a", Serializable.yes));
    ...
}

I wonder to what extent the in-language solution can be made to work.


Andrei
March 16, 2012
Re: Proposal: user defined attributes
On Friday, 16 March 2012 at 16:09:55 UTC, Andrei Alexandrescu 
wrote:
> I wonder to what extent the in-language solution can be made to 
> work.

It can sort of work, but it isn't very good.

(I thought you might say this too; check out this post:
http://forum.dlang.org/thread/bccwycoexxykfgxvedix@forum.dlang.org#post-gmtawdmaihzgxnvezfrf:40forum.dlang.org 
)


In that other post, I talked about function parameters (which
is what I really want it for). The in-language solution for that
can only be made to work with a pile of fragile hacks.

But, even for functions, you just wrote:

    int a;
    mixin(note("a", Serializable.yes));

OK, that works... but what about:

    int a() { return 0; }
    mixin(note("a", Serializable.yes));


We're good so far... until:

    int a() { return 0; }
    int a(int b) { return 0; }
    mixin(note("a", Serializable.yes)); // which a?


You could potentially solve that by using the mangle of
instead of the string, and doing an alias template rather
than passing a string directly, but I'm not sure if we actually
can in today's dmd (I don't even know how to refer to
the proper overload...)

Also, what if the declaration is anonymous? You could
still see it in reflection - a ParameterTypeTuple doesn't
care about names - but you couldn't annotate it this
way. I guess a workaround there would be giving it a
name like _param_0.



Moreover, the mixin adds some junk to the struct. If
you iterate over allMembers, will you see

  enum _note_a_serializable;

in there? If there's a consistent naming convention,
we could skip it (my web.d ignores everything with a
leading underscore, for example), but it is nevertheless
letting implementation details slip out in reflection.

Seeing how the whole point of this is to be used in reflection,
that's a case I think is likely to cause annoyance in practice.



The current language solution isn't really *bad* with enough
library help, but it isn't particularly *good* either and I
don't think it can be. I've tried a few things, and I still
see the lack of user annotations as D's biggest miss right now.
« First   ‹ Prev
1 2 3 4 5
Top | Discussion index | About this forum | D home