April 06, 2012
On Fri, 06 Apr 2012 15:06:47 -0400, Piotr Szturmaj <bncrbme@jadamspam.pl> wrote:

> Steven Schveighoffer wrote:
>> On Fri, 06 Apr 2012 13:33:33 -0400, Tove <tove@fransson.se> wrote:
>>> I think this proposal pretty much covers what I would expect from
>>> 'custom attributes'... but what about adding a D twist, getting "what
>>> we annotate" as a template parameter so that one among other things
>>> can make use of Template Constraints?
>>
>> Interesting, so something like:
>>
>> @attribute string defaultName(T)() if(is(typeof(T.init.name))) { return
>> T.init.name;}
>>
>> Not sure how much this gives us, but it definitely feels doable.
>
> See also: http://forum.dlang.org/post/jlmtcv$v09$1@digitalmars.com

Excellent point, passing the symbol being annotated (probably should be an alias) should definitely be added.  Then you could easily limit what attributes can be attached to!

-Steve
April 06, 2012
Am 06.04.2012 20:52, schrieb Steven Schveighoffer:
> On Fri, 06 Apr 2012 14:23:45 -0400, Mafi <mafi@example.org> wrote:
>
>>
>> There's one difference I think.
>> struct approach:
>> struct Area { int x, y; }
>> Area sqare(int a) { return Area(x, y); }
>> //foo and bar are attributed the same
>> @Area(5, 5) int foo();
>> @square(5) int bar();
>>
>> whereas with the function approach:
>> @area(5, 5) int foo();
>> @square(5) int bar();
>> foo and bar have different attributes.
>>
[...]
> Also, if I see:
>
> @square(5) int foo();
>
> How do I know that I have to use __traits(getAttribute, foo, Area)?
>
> Another possibility:
>
> @attribute Area area(int w, int h) { return Area(w, h);}
> @attribute Area area(Area a) { return a;}
>
> Area square(int a) { return Area(a, a);}
>
> @area(5, 5) int foo();
> @area(square(5)) int bar();
>
> -Steve

The second possibility looks good. Especially because the lack of @attribute on square disallows @square.

Mafi
April 06, 2012
Steven Schveighoffer wrote:
> On Fri, 06 Apr 2012 15:06:47 -0400, Piotr Szturmaj
>> See also: http://forum.dlang.org/post/jlmtcv$v09$1@digitalmars.com
>
> Excellent point, passing the symbol being annotated (probably should be
> an alias) should definitely be added. Then you could easily limit what
> attributes can be attached to!

Yes, I forgot to add "alias". I think this is the best approach to support attribute constraints. We can write some library mixins to support common cases like limiting the number of attributes or allowing simple targets (if we choose UDTs for attributes):

struct Attr
{
    // allows multiple attachments, but for classes and structs only
    mixin AttrConstraint!(true, AttrTarget.Class | AttrTarget.Struct);
}
April 06, 2012
Steven Schveighoffer wrote:
> On Fri, 06 Apr 2012 15:03:39 -0400, Piotr Szturmaj
> <bncrbme@jadamspam.pl> wrote:
>
>> Steven Schveighoffer wrote:
>
>>> What if I have 20 string attributes, I must define a new attribute type
>>> for each one? This seems like unneeded bloat.
>>
>> I don't see advantage of functions here, twenty of them is also a
>> bloat. Different types give you different _names_ for different
>> purposes. Those _names_ are crucial to support the attributes.
>
> Unused function do not make it into the EXE.

Are unused structs compiled into EXE?

>> How do you get list of all attributes with your function based
>> proposal? You can get a string attribute but you don't know which
>> function generated it. You don't know if it was serializable, author
>> or whatever.
>
> foreach(name, value; __traits(getAttributes, symbol)) {...}
>
> hereby added to the proposal.

Ok, but how do you filter that and pass the result to another template? It should be easy if __traits(getAttributes, symbol) would return an expression tuple, which is what I'd like to see.

>>> BTW, if I wasn't trying to demonstrate that you could store structs, I
>>> would have written:
>>>
>>> @attrubte string author(string name) { return name;}
>>>
>>> and save the extra bloat associated with declaring another type. Maybe
>>> we could even get this someday:
>>
>> As above, declaring another function is also a bloat.
>
> No, it doesn't generate more typeinfo that must go into the EXE. When
> the EXE is built, all associated bloat should disappear, it's only
> needed during compilation.

Those types are only needed during compilation too. However, I don't know if they're always included into binary or only when they're used.

>>> @attribute author(string name) => name;
>>>
>>> I just don't see the need to declare a type that can wrap a string.
>>>
>>> You could even add this rule:
>>>
>>> if @attribute is placed on a struct, its constructor becomes an
>>> @attribute qualified function with the name of the struct as the
>>> attribute name.
>>
>> Consider struct constructors as equivalent of functions proposed by
>> you. Here you declare a type, there you declare a function. They're
>> very similar, besides that struct type _describes_ the attribute. A
>> function on the other side just returns a value, which doesn't have
>> any name attached to it.
>
> The name is the function. You seem to be arguing the equivalent of:
>
> "int x is useless. It should really just be int. If you need another
> integer field, make a new type that's just like int, how hard is that?"
>
> Yeah, I know it's a strawman, but this is seriously how it sounds to me ;)

If you have simple attributes in mind, like name = string value, then yes, but most attributes are not that simple. Most of the time you will be forced to create a struct anyway (and return it from function).

>>> It's an example. you can choose any type you want! I actually just want
>>> the name of the author, I don't care whether that's a struct, or a
>>> string.
>>
>> Yes, but my point is that you get a bool and you don't know which of
>> the functions returned it, as many of them can return bool.
>
> I think you are missing how the metadata is stored as key-value pairs,
> with the key being the name of the function that was used.

Ok, but it needs more work in the compiler, comparing to identifier search and remembering expression tuple of a symbol. Also, I just found a major drawback of this approach: consider parameterless attributes like @NotNull. What would you return from function named NotNull()?

>>> Any CTFE computed value should suffice.
>>
>> I think that list of attributes should be a list of user defined
>> types. You can always write a function to construct them, but anyway
>> you get named user defined type (like struct). Named type easily
>> disambiguates between different attributes without resorting to
>> name-value solutions.
>
> Again, see point above about not naming variables.
>
>> This is how it's done in C# by the way.
>
> Yes I know. I don't think we need to limit ourselves this way, C# does
> not have the compile-time power that D does.

I didn't state that we shouldn't use compile-time :)
April 07, 2012
Le 06/04/2012 15:23, Steven Schveighoffer a écrit :
> OK, so I woke up this morning to find a huge discussion on attributes,
> and I'd like to once again propose the idea of how to define and use an
> attribute.
>
> I do not like the idea of:
>
> @attr(identifier)
>
> Why? Because what the hell did we create that "@" syntax for? I though
> it was to avoid misinterpreting such things as normal symbols, and avoid
> creating new keywords. Why should the compiler be the only one able to
> use such symbols?
>
> Another thing I don't like is some idea that only a certain type of
> construct can be the identifier. An attribute should have one
> requirement -- that it can be created/determined at compile time.
>
> So here is my proposal:
>
> 1. Introduce a new compiler-defined attribute @attribute (or @attr or
> something better, the name isn't important).
> 2. This attribute can *only* be used on a module-level function.
> 3. @attribute functions *must* be CTFE-able.
> 4. An @attribute function can be used as a user-defined attribute on any
> declaration using the syntax @identifier where identifier is the name of
> the attribute function (subject to normal function lookup rules). If the
> attribute can be called without parameters, the parentheses are optional.
> 5. When used on a declaration, that CTFE function is called during
> compile-time, and the result of that function is stored as metadata on
> that symbol. It does not affect the type of the symbol or transfer to
> any other symbols that are assigned to the value of that declaration (in
> other words, it *cannot* be used as a type constructor).
> 6. The metadata is stored in a key-value pair, with the key being the
> symbol of the @attribute function, and the value being the result of the
> CTFE function.
> 7. One can lookup whether an attribute exists on a symbol using
> __traits(hasAttribute, symbol).
> 8. One can retrieve the value of the CTFE result using
> __traits(getAttribute, symbol). If the CTFE function returns void, this
> is a compiler error.
>
> And that's it. We can extend this eventually to storing something in
> TypeInfo, but I'm not sure we need that. However, I want to stress that
> having runtime type metadata is not a requirement for this proposal.
>
> Example usage:
>
> @attribute bool serializable(bool yesorno = true) { return yesorno; }
>
> unittest {
> // serializable is a normal function also
> assert(serializable() == true);
> assert(serializable(true) == true);
> assert(serializable(false) == false);
> }
>
> @serializable struct MyType
> {
> int x;
> int y;
> @serializable(false) int z;
> }
>
> string serialize(T)(const ref T t) if (__traits(hasAttribute,
> serializable) && __traits(getAttribute, serializable))
> {
> // serialize each field. Skip any fields that are marked as serializable
> == false
> }
>
> -Steve

The struct proposal from previous thread was superior because it provide similar capability without as much language change.
April 07, 2012
On Friday, 6 April 2012 at 13:23:03 UTC, Steven Schveighoffer wrote:
> OK, so I woke up this morning to find a huge discussion on attributes, and I'd like to once again propose the idea of how to define and use an attribute.
>
> I do not like the idea of:
>
> @attr(identifier)
>
> Why?  Because what the hell did we create that "@" syntax for?  I though it was to avoid misinterpreting such things as normal symbols, and avoid creating new keywords.  Why should the compiler be the only one able to use such symbols?
>
> Another thing I don't like is some idea that only a certain type of construct can be the identifier.  An attribute should have one requirement -- that it can be created/determined at compile time.

Either this or the one that's the same just with structs is the way to go. The original proposal by Walter is good, it's just a little verbose.

I slightly prefer this function method over the struct method because:
1) No need to generate a custom struct for everything. Plenty of things are just a true or false, or a string. Saves a little bit of TypeInfo generation.
2) The more important one: The possibility to eventually include an alias template parameter. This allows things like looking up whether the symbol with the attribute has other attributes applied, or determining type. This allows things like constraints, and can be a nice benefit.

On the topic of type vs storage, it is useful to be able to apply attributes to a type, but this should /not/ change the type itself. It must be transparent to the user what attributes a type has unless they're actually accessing attributes.

April 07, 2012
Am Fri, 06 Apr 2012 16:33:21 -0400
schrieb "Steven Schveighoffer" <schveiguy@yahoo.com>:

> On Fri, 06 Apr 2012 15:03:39 -0400, Piotr Szturmaj <bncrbme@jadamspam.pl> wrote:
> 
> > Steven Schveighoffer wrote:
> 
> >> What if I have 20 string attributes, I must define a new attribute type for each one? This seems like unneeded bloat.
> >
> > I don't see advantage of functions here, twenty of them is also a bloat. Different types give you different _names_ for different purposes. Those _names_ are crucial to support the attributes.
> 
> Unused function do not make it into the EXE.

But as long as you mark attribute structs in some special way (@attribute struct Author), this can also be guaranteed for structs. Afaik ignoring unused functions is currently done by the linker, but I think functions/structs only used for attributes should probably never make it to the linker. I think we already had cases were linkers didn't strip unused functions for some reason?

Regarding code bloat: If struct initializers could be used with attributes, a constructor isn't necessary and the minimal code is:
------
@attribute struct Author
{
    string name;
}

@Author("Johannes Pfau") int test;
or
@Author{name: "Johannes Pfau"} int test;
------

That's exactly as much code as using functions:
------
@attribute string Author(string name)
{
    return name;
}

@Author("Johannes Pfau") int test;
------


I don't have a strong opinion whether storable types should be limited to structs, but I think classes add little benefit and complicate things because of inheritance (at least if you query attributes by type). What we want to do here is store _data_ and the D style is to use structs for pure data. Basic types could be useful too but when querying by type, those can't work well.


BTW: I think there's one thing both your and my proposals are missing:

The function/constructor returning the data must be pure: We can't guarantee this function will be executed only once, but the value of the attribute should always be the same. Consider this scenario:

a.d
----
@RandomNumber() int test;
----

b.d
---
auto value1 = __traits(getAttribute, a.test, RandomNumber);
---

c.d
---
auto value2 = __traits(getAttribute, a.test, RandomNumber);
---

All files are compiled individually. Now the compiler has to call RandomNumber() 2 times: one time for b.d and one time for c.d, but value1 should be the same as value2.


April 07, 2012
Le 07/04/2012 05:29, Kapps a écrit :
> On the topic of type vs storage, it is useful to be able to apply
> attributes to a type, but this should /not/ change the type itself. It
> must be transparent to the user what attributes a type has unless
> they're actually accessing attributes.
>

Then put the attribute at type declaration, not where it is used.
April 07, 2012
Le 07/04/2012 05:29, Kapps a écrit :
> I slightly prefer this function method over the struct method because:
> 1) No need to generate a custom struct for everything. Plenty of things
> are just a true or false, or a string. Saves a little bit of TypeInfo
> generation.

If the type isn't used at runtime, the compiler should be able to remove that dead part of the code. If it doesn't, it is an implementation issue, and shouldn't be solved by language design decision.
April 07, 2012
Le 07/04/2012 09:10, Johannes Pfau a écrit :
> But as long as you mark attribute structs in some special way
> (@attribute struct Author), this can also be guaranteed for structs.
> Afaik ignoring unused functions is currently done by the linker, but I
> think functions/structs only used for attributes should probably never
> make it to the linker. I think we already had cases were linkers didn't
> strip unused functions for some reason?
>
> Regarding code bloat: If struct initializers could be used with
> attributes, a constructor isn't necessary and the minimal code is:
> ------
> @attribute struct Author
> {
>      string name;
> }
>
> @Author("Johannes Pfau") int test;
> or
> @Author{name: "Johannes Pfau"} int test;
> ------
>
> That's exactly as much code as using functions:
> ------
> @attribute string Author(string name)
> {
>      return name;
> }
>
> @Author("Johannes Pfau") int test;
> ------
>
>
> I don't have a strong opinion whether storable types should be limited
> to structs, but I think classes add little benefit and complicate
> things because of inheritance (at least if you query attributes by
> type). What we want to do here is store _data_ and the D style is to
> use structs for pure data. Basic types could be useful too but when
> querying by type, those can't work well.
>

For basic type :

alias int foobar;
@foobar(2) someDeclaration;

No benefit in introducing a new syntax.

> BTW: I think there's one thing both your and my proposals are missing:
>
> The function/constructor returning the data must be pure: We can't
> guarantee this function will be executed only once, but the value of
> the attribute should always be the same. Consider this scenario:
>
> a.d
> ----
> @RandomNumber() int test;
> ----
>
> b.d
> ---
> auto value1 = __traits(getAttribute, a.test, RandomNumber);
> ---
>
> c.d
> ---
> auto value2 = __traits(getAttribute, a.test, RandomNumber);
> ---
>
> All files are compiled individually. Now the compiler has to call
> RandomNumber() 2 times: one time for b.d and one time for c.d, but
> value1 should be the same as value2.
>

RandomNumber is something that shouldn't be CTFEable, if it does what the name says.