Thread overview
Question about UDAs
Aug 03, 2020
Cecil Ward
Aug 03, 2020
Adam D. Ruppe
Aug 04, 2020
H. S. Teoh
Aug 04, 2020
Ali Çehreli
Aug 04, 2020
H. S. Teoh
August 03, 2020
When practically speaking would you use UDAs? A real-world use-case? I’ve seen them in use already for core language features instead of keywords like "pure", and I suppose this choice keeps the number of keywords down and the result is perhaps easier to extend. The motivation for these usages is clear but I don’t understand how I might use them in my own code. Ali Çehreli’s book mentions them briefly with an example but that doesn’t seem to qualify as a realistic use-case.
August 03, 2020
On Monday, 3 August 2020 at 03:00:08 UTC, Cecil Ward wrote:
> When practically speaking would you use UDAs? A real-world use-case?

They are useful when you want to attach some kind of metadata to the declarations for a library to read. For example, my script.d looks for `@scriptable` for methods that it should expose to the script user. My cgi.d uses things like `@URLName("foo")` and/or `@DisplayName("whatever")` if you want to override the auto-wrapper's default strings.

My day job work project uses it for user-visible documentation of their functions too, accessible from a command line interface.


> I’ve seen them in use already for core language features instead of keywords like "pure"

i wouldn't call those UDAs because they aren't user-defined... but yeah it is the same syntax there basically too.
August 03, 2020
On Mon, Aug 03, 2020 at 03:00:08AM +0000, Cecil Ward via Digitalmars-d-learn wrote:
> When practically speaking would you use UDAs? A real-world use-case?

There are probably more use cases than this, but for me, their primary usefulness is in declarative programming and compile-time introspection.

Here's one specific use case: serialization.  I have a bunch of structs and classes that I want to serialize, and instead of writing tons and tons of boilerplate, I use __traits(allMembers) to introspect a generic type T and generate serialization code for it.  But sometimes some fields should not be serialized (e.g., they are transients like caches and stuff).  Or sometimes certain fields may need special treatment, like a different serialization method depending on domain-specific information about their contents.

I *could* hard-code this knowledge into the serialization code, but UDAs provide a better alternative: I tag my types with various UDAs recognized by the serialization system, so that when it encounters, say, a string tagged @hexDigits, it knows that it can use a more compact representation by parsing the string into binary and storing it as a compact blob, for example.  Or if a field should not be serialized, I'd tag it @dontSerialize and the serialization code skips over it. By using UDAs instead of hard-coding into the serialization code, the serialization can be made generic and reusable across projects.

Other use cases include automatically creating database schemas based on types: like a bunch of structs representing records, and UDAs to tag which fields should be indexed, which fields have constraints, etc.. Then the database backend code can just introspect these types and automatically generate schemas, query code, etc..

Since UDAs can be arbitrary types, it actually has a lot of uses. For example, you can use them to inject code for processing data, e.g., by using a struct as UDA with a method that takes the data and performs some operation on it. The generic code that reads the UDA can then pass the data to the method in a completely agnostic way that lets you separate concerns very cleanly.


T

-- 
In a world without fences, who needs Windows and Gates? -- Christian Surchi
August 03, 2020
On 8/2/20 8:00 PM, Cecil Ward wrote:

> Ali Çehreli’s book mentions them briefly with an example
> but that doesn’t seem to qualify as a realistic use-case.

The XML example I chose there qualifies as serialization like H. S. Teoh mentions. UDAs on user-defined type members are for marking them for later introspection in use cases like "do this for all members but take UDAs into account." For example, the UDA in my example contributes as "serialize all members as XML but obfuscate the members that have a special UDA."

UDAs were added to D by a request from Manu Evans and that's when I learned them. In one of Manu's use cases they would put a @Tweakable attribute to certain struct members. The effect of that attribute would be to compile special code that would expose that member in a dialog box where the developer would "tweak" its value to see how the program (a game) would behave at specific values of that member.

The awesomeness comes from the fact that once they have this @Tweakable machinery, they don't change their code at all: They put that attribute to certain members during development, find good values and then remove it; perhaps in half an hour. The only addition to code is one @Tweakable attribute and some magic produces a dialog box; then they remove the attribute. Pretty cool. :)

Manu's presentatian is available here:

 https://www.youtube.com/watch?v=FKceA691Wcg

Slide 25 at minute 18:30 is one spot he talks about it.

Ali


August 03, 2020
On Mon, Aug 03, 2020 at 08:16:57PM -0700, Ali Çehreli via Digitalmars-d-learn wrote: [...]
> UDAs were added to D by a request from Manu Evans and that's when I learned them. In one of Manu's use cases they would put a @Tweakable attribute to certain struct members. The effect of that attribute would be to compile special code that would expose that member in a dialog box where the developer would "tweak" its value to see how the program (a game) would behave at specific values of that member.
> 
> The awesomeness comes from the fact that once they have this @Tweakable machinery, they don't change their code at all: They put that attribute to certain members during development, find good values and then remove it; perhaps in half an hour. The only addition to code is one @Tweakable attribute and some magic produces a dialog box; then they remove the attribute. Pretty cool. :)

I've also used it for generating getopt-like code for parsing command-line parameters.  You might have several subsystems in your program, each of which comes with a set of parameters that can be configured; instead of sprinkling this information across multiple places (once in the subsystem to read the values, once in the call to getopt to parse the option, once in the code for display detailed description of the setting, once in configuration file parsing code to basically do the same thing as getopt except with a config file, ad nauseaum), just create a struct that contains the parameters for each subsystem, then use UDAs to decorate each setting with command-line option name, help text, value ranges, or even custom parsing functions if you want to get fancy. Then write a generic option-parsing function that introspects the UDAs to generate help text, option names, default values, precedences, etc.. Another generic function for parsing the config file.

Then the next time you want to add a setting, it's just a matter of adding another field to your struct, tag it with the appropriate UDAs, and it will "magically" appear in your command-line, config file parsing, and in-program help text without further ado.

Best of all, once you build this infrastructure, you can easily reuse it across different programs: the getopt wrapper, help text generator, config file parser are all completely driven by introspection and UDAs, so there's nothing program-specific about them. Just copy-n-paste them into another project, and create your structs, and you're all set. :-)


T

-- 
"I'm not childish; I'm just in touch with the child within!" - RL