www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Why are you using `std.traits.fullyQualifiedName`?

reply Dennis <dkorpel gmail.com> writes:
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
Jan 20 2023
next sibling parent reply Salih Dincer <salihdb hotmail.com> writes:
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: ```d 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
Jan 20 2023
parent Salih Dincer <salihdb hotmail.com> writes:
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) : ```d 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
Jan 20 2023
prev sibling next sibling parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
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)
Jan 20 2023
parent Adam D Ruppe <destructionator gmail.com> writes:
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.
Jan 21 2023
prev sibling next sibling parent reply Atila Neves <atila.neves gmail.com> writes:
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
Jan 22 2023
parent Adam D Ruppe <destructionator gmail.com> writes:
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. 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.
Jan 23 2023
prev sibling parent reply Nicholas Wilson <iamthewilsonator hotmail.com> writes:
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.
Jan 23 2023
parent Adam D Ruppe <destructionator gmail.com> writes:
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.
Jan 24 2023