Thread overview
Why are you using `std.traits.fullyQualifiedName`?
Jan 20, 2023
Dennis
Jan 21, 2023
Salih Dincer
Jan 21, 2023
Salih Dincer
Jan 21, 2023
Adam D Ruppe
Jan 22, 2023
Atila Neves
Jan 23, 2023
Adam D Ruppe
Jan 24, 2023
Nicholas Wilson
Jan 25, 2023
Adam D Ruppe
January 20, 2023

There's a Pull Request to turn Phobos' std.traits.fullyQualifiedName into a trait __traits(fullyQualifedName). Because Phobos' implementation expands a lot of templates, the idea is to reduce compile times by implementing it in the compiler instead. However, there's some discussion around it, because Adam Ruppe considers it a function that shouldn't be used, because it's poorly defined and is prone to mistakenly be used for meta programming.

>

I've never - not once - seen a case where people said FQN was necessary where they were actually correct about it. It is a misfeature that encourages bad code.

Hence the question in the title:
Are you using std.traits.fullyQualifiedName, and if so, what do you use it for?

Relevant links:
https://github.com/dlang/dmd/pull/14711#issuecomment-1396290841
https://github.com/dlang/dlang.org/pull/3495#issuecomment-1396295627

January 21, 2023

On Friday, 20 January 2023 at 23:14:39 UTC, Dennis wrote:

>

Are you using std.traits.fullyQualifiedName, and if so, what do you use it for?

You are talking about [this](https://dlang.org/phobos/std_traits.html#fullyQualifiedName

I DO not use...

__traits and typeid with integrated properties are enough for me:

class S
{
    short n;
    alias n this;
    this(short n) { this.n = n; }
}

void print(alias T)()
{
    import std.stdio : writeln;
    TypeInfo name = typeid(T);
             name.writeln;

    import std.traits : fullyQualifiedName;
    
    fullyQualifiedName!T.writeln;
    T test = [ new S(41), new S(42) ];
    __traits(getPointerBitmap, S).writeln(": ", test);
}

void main() {
    alias arrS = const S[];
    print!arrS;

    class S { short s; }
    auto test = new S;

    assert(__traits(getPointerBitmap, S) == [32, 8]);
    assert(is(
        typeof(S.s) == short)
    );
} /*
const(const(onlineapp.S)[])
const(onlineapp.S[])
[18, 0]: [const(onlineapp.S), const(onlineapp.S)] //*/

SDB@79

January 21, 2023

On Saturday, 21 January 2023 at 00:48:01 UTC, Salih Dincer wrote:

>

__traits and typeid with integrated properties are enough for me:

Oh, there's also this (Ha bir de bu var in Turkish) :

struct Str
{
  alias ToString this;
  size_t s;
  string ToString() const
  {
    import std.conv : text;
    return text(s);
  }
}
    
enum Params { width = 100 }
auto String = Str(Params.width);
import std.conv;

void main()
{
  string[] data;
  data ~= __traits(identifier, String);
  data ~= typeof(String).stringof;
  data ~= String;
  data ~= Params.width.to!string;
  data.writeln; // ["String", "Str", "100", "width"]
}

D gives me everything to create a big world (Dig Big!). Thanks: D!

SDB@79

January 21, 2023
You shouldn't use fullyQualifiedName to do symbol lookup, that is where Adam is coming from (it is very likely to cause issues).

However I strongly believe that he is wrong about it shouldn't exist. It should, it needs to exist, its a basic introspection ability.

There are two areas where I use it:

1) Uniquely identifying a type (registration of types for things like serializing, passing to hash function ext.).
2) Pretty printing for debugging. Here is some output that I generated yesterday:



- sidero.base.datetime.time.timezone.TimeZone(
	state: sidero.base.datetime.time.timezone.TimeZone.State@1E3A01E4F00(
		refCount: 6,
		allocator: sidero.base.allocators.api.RCAllocator(
			---- ignoring ----
			private refAdd_,
			private refSub_,
			private allocate_,
			private deallocate_,
			private reallocate_,
			private owns_,
			private deallocateAll_,
			private empty_),
		name: "Pacific/Auckland",
		haveDaylightSavings: true,
		source: sidero.base.datetime.time.timezone.TimeZone.Source.Windows,
		fixedBias: 0,
		windowsBase: sidero.base.datetime.time.internal.windows.WindowsTimeZoneBase(
			dtzi: sidero.base.datetime.time.internal.windows.DYNAMIC_TIME_ZONE_INFORMATION(
				Bias: -720,
				StandardName: "New Zealand Standard Time\0ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿",
				StandardDate: core.sys.windows.winbase.SYSTEMTIME(
					wYear: 0,
					wMonth: 4,
					wDayOfWeek: 0,
					wDay: 1,
					wHour: 3,
					wMinute: 0,
					wSecond: 0,
					wMilliseconds: 0),
				StandardBias: 0,
				DaylightName: "New Zealand Daylight Time\0ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿",
				DaylightDate: core.sys.windows.winbase.SYSTEMTIME(
					wYear: 0,
					wMonth: 9,
					wDayOfWeek: 0,
					wDay: 5,
					wHour: 2,
					wMinute: 0,
					wSecond: 0,
					wMilliseconds: 0),
				DaylightBias: -60,
				TimeZoneKeyName: "New Zealand Standard Time\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 

				DynamicDaylightTimeDisabled: false),
			stdName: "New Zealand Standard Time",
			dstName: "New Zealand Daylight Time",
			standardOffset: sidero.base.datetime.time.internal.windows.WindowsTimeZoneBase.Bias(
				seconds: 0,
				appliesOnDate: sidero.base.datetime.calendars.gregorian.GregorianDate(
					---- ignoring ----
					private year_,
					private month_,
					private day_ ->
1/4/2023),
				appliesOnTime: sidero.base.datetime.time.timeofday.TimeOfDay(
					---- ignoring ----
					private hour_,
					private minute_,
					private second_,
					private msec_ ->
03:00:00.000000)),
			daylightSavingsOffset: sidero.base.datetime.time.internal.windows.WindowsTimeZoneBase.Bias(
				seconds: -3600,
				appliesOnDate: sidero.base.datetime.calendars.gregorian.GregorianDate(
					---- ignoring ----
					private year_,
					private month_,
					private day_ ->
5/9/2023),
				appliesOnTime: sidero.base.datetime.time.timeofday.TimeOfDay(
					---- ignoring ----
					private hour_,
					private minute_,
					private second_,
					private msec_ ->
02:00:00.000000))),
		ianaTZBase: sidero.base.datetime.time.internal.iana.IanaTZBase(
			tzFile: no-error but null,
			startUnixTime: 0,
			endUnixTime: 0,
			transitionsForRange: sidero.base.containers.dynamicarray.DynamicArray!(sidero.base.datetime.time.internal.iana.TZFile.Transition)(
				---- ignoring ----
				private state,
				private minimumOffset,
				private maximumOffset [])),
		posixTZBase: sidero.base.datetime.time.internal.posix.PosixTZBase(
			loadFromTZifFile: null,
			stdName: null,
			dstName: null,
			stdOffset: 0,
			dstOffset: 0,
			transitionToStd: sidero.base.datetime.time.internal.posix.PosixTZBaseRule(
				type: sidero.base.datetime.time.internal.posix.PosixTZBaseRule.Type.NoDST,
				weekOfMonth: 0,
				dayOfWeek: 0,
				secondsBias: 0
				---- ignoring ----
				union julianDay,
				union dayOfYear,
				union monthOfYear),
			transitionToDST: sidero.base.datetime.time.internal.posix.PosixTZBaseRule(
				type: sidero.base.datetime.time.internal.posix.PosixTZBaseRule.Type.NoDST,
				weekOfMonth: 0,
				dayOfWeek: 0,
				secondsBias: 0
				---- ignoring ----
				union julianDay,
				union dayOfYear,
				union monthOfYear))) ->
Pacific/Auckland)
January 21, 2023
On Saturday, 21 January 2023 at 05:21:15 UTC, Richard (Rikki) Andrew Cattermole wrote:
> 1) Uniquely identifying a type (registration of types for things like serializing, passing to hash function ext.).

the compiler uses .mangleof for this purpose and druntime uses typeid(). FQN is just an even bigger, but no more unique, presentation of mangleof.

> 2) Pretty printing for debugging. Here is some output that I generated yesterday:
>
> - sidero.base.datetime.time.timezone.TimeZone(

This case is trivial to get out of existing language capabilities (including very, very quickly by slicing into mangleof).

Why is the Phobos implementation so complicated then? Well, some of it is unnecessary complexity, but much of it has to do with the various different kind of template arguments (and most the rest of it is due to function overloads). And these can again be extracted from mangleof, I said this in the github thread, but many times these actually harm readability.

For debugging, there's a good chance you'd actually rather have a partially qualified name tied together with a source location. Which is more likely to help debugging:

random.d:1081 std.random.Mt19937

or

std.random.MersenneTwisterEngine!(uint, 32, 624, 397, 31, 0x9908b0df, 11, 0xffffffff, 7, 0x9d2c5680, 15, 0xefc60000, 18, 1_812_433_253)

?

(Phobos and the trait in the PR picks the latter.)

Now, I'll grant this is a bit unfair because the compiler never exposes alias names anymore (though it used to, and I consider this a regression, it broke some of my code that used to reflect on those :( ). But even:

random.d:1081 std.random.MersenneTwisterEngine

without the arguments listed is better for debugging. And this is a simple one. In the PR thread, I mentioned `structFromTable!(import("db.sql"), "MyTable");` That representation in source isn't too bad. The definition point is pretty readable. The fullyQualifiedName is dozens of kilobytes dumped to the screen. Is this useful?

A trait to get the aliased name would be far more useful! At least the global aliased name; a local one like `template a(alias A) {}` always returning A not that interesting, but if the alias name is in an outer scope, that's actually valuable information.

Now, I will grant there are some places where template args help:

struct Thing {
  Nullable!int a;
  Nullable!string b;
}


If that just printed `std.typecons.Nullable` as the type, you are missing something. How do you know which one is worth printing and which one isn't? It probably depends... which means it is better done by library code.

At the same time, there is something the compiler can potentially do here, and again, I mentioned this in the PR thread: it could use its knowledge of scopes to disambiguate.

It could print this as Nullable or Nullable!int unless there's another Nullable in scope, in which case it prints the module name - std.typecons.Nullable - to clear it up. The compiler has the knowledge and the existing code to do this for error messages. So exposing it might be of some value.


But .... we need to identify the concrete intended use case. Error messages and debugging aren't even exactly the same and they're very different than code generation.

Just.... since we have the library capabilities to do all this with as much or as little customization as desired, does the language need to bloat it up offering a bunch of options?

But regardless, the one option it proposes to offer right now is not ideal for any use case.
January 22, 2023

On Friday, 20 January 2023 at 23:14:39 UTC, Dennis wrote:

>

There's a Pull Request to turn Phobos' std.traits.fullyQualifiedName into a trait __traits(fullyQualifedName). Because Phobos' implementation expands a lot of templates, the idea is to reduce compile times by implementing it in the compiler instead. However, there's some discussion around it, because Adam Ruppe considers it a function that shouldn't be used, because it's poorly defined and is prone to mistakenly be used for meta programming.

>

I've never - not once - seen a case where people said FQN was necessary where they were actually correct about it. It is a misfeature that encourages bad code.

Hence the question in the title:
Are you using std.traits.fullyQualifiedName, and if so, what do you use it for?

Relevant links:
https://github.com/dlang/dmd/pull/14711#issuecomment-1396290841
https://github.com/dlang/dlang.org/pull/3495#issuecomment-1396295627

I use it all the time, especially since I do so much with reflection. I need a unique name for types to store, to show to the user when something goes wrong, to avoid name clash issues due to imports in generated code, ...

Atila

January 23, 2023
On Sunday, 22 January 2023 at 15:36:29 UTC, Atila Neves wrote:
> I use it all the time, especially since I do so much with reflection.

I probably do even more with reflection, and yet have only used fullyQualifiedName once in all my code (and that usage could easily be replaced with something else; it is just an arbitrary key in a database).

Creating web apis and user interfaces out of D classes? No FQN.

Creating script language bindings out of arbitrary D declarations? No FQN.

Creating dynamic library loaders out of D interfaces? No FQN.

Creating bi-directional bridges between D and Java or C# code? No FQN.

Custom unittesting? No FQN.

Making maps of D modules to instantiate classes from XML files? No FQN.

Custom event loop message box that takes arbitrary types and dispatches them? No need for FQN.

I've tried to look at your mirror library, but it is almost entirely undocumented, which makes it difficult to evaluate. The only packages that use it on dub are glue-d (which has zero users and unmaintained for nearly three years) and autowrap, which is a binding generator, something I've done many times. It is not clear why fullyQualifiedName is necessary or in any way beneficial for this task.


> I need a unique name for types to store

Use .mangleof or typeid(), depending on the circumstance. That's what dmd and druntime do.

> to show to the user when something goes wrong

Make a partially qualified name out of the identifier, it will be more readable in the general case. That's what dmd does.

> to avoid name clash issues due to imports in generated code

Use local names instead and this is not a problem. If you are seeing clashes, that's an indication that you're fighting the language instead of working with it. This is often where the wins come from using better approaches.

January 24, 2023

On Friday, 20 January 2023 at 23:14:39 UTC, Dennis wrote:

>

There's a Pull Request to turn Phobos' std.traits.fullyQualifiedName into a trait __traits(fullyQualifedName). Because Phobos' implementation expands a lot of templates, the idea is to reduce compile times by implementing it in the compiler instead. However, there's some discussion around it, because Adam Ruppe considers it a function that shouldn't be used, because it's poorly defined and is prone to mistakenly be used for meta programming.

>

[...]

Hence the question in the title:
Are you using std.traits.fullyQualifiedName, and if so, what do you use it for?

Relevant links:
https://github.com/dlang/dmd/pull/14711#issuecomment-1396290841
https://github.com/dlang/dlang.org/pull/3495#issuecomment-1396295627

I use a hacked down version for symbol disambiguation in mixins. I don't use std.traits.fullyQualifiedName precisely because it is too slow.

January 25, 2023
On Tuesday, 24 January 2023 at 03:57:49 UTC, Nicholas Wilson wrote:
> I use a hacked down version for symbol disambiguation in mixins.

Yes, and your code is better off without it, as I've demonstrated in particular, as code refactored to use local names has fewer (and simpler) lines of code since it no longer requires the import hacks, compiles significantly faster since it no longer requires the calls to format(), and would be easier to extend to other uses (not super relevant since it is for internal use only in your project, but if that were to change, you'd hit even more trouble with your current approach that are all solved by the local alias approach).

This is my biggest objection to enshrining this particular function in the compiler: it encourages suboptimal code, when the language already has better alternatives!

My second objection is that it is under-specified, so even if you did want to use it, you'd technically be relying on unspecified behavior.