May 03, 2022
On Tue, May 03, 2022 at 04:38:53PM +0200, Arafel via Digitalmars-d-learn wrote:
> On 3/5/22 15:57, Adam D Ruppe wrote:
> > So doing things yourself gives you some control.
> 
> Yes, it is indeed possible (I acknowledged it), but I think it's much more cumbersome than it should, and puts the load on the user.
> 
> If templated this worked in static context (ideally everywhere else too), then we'd be able to implement RTTI in a 100% "pay as you go" way: just inherit from SerializableObject, or perhaps add a mixin to your own root class, and that'd be it.
> 
> Actually, it would be cool to do it through an interface, although I don't think an interface's static constructors are invoked by the implementing classes... it would be cool, though.

The way I did it in my own serialization code is to use CRTP with static ctors in templated wrapper structs.

Namely, replace:

	class Base { ... }
	class Derived : Base { ... }

with:

	class Base : Serializable!(Base) { ... }
	class Derived : Serializable!(Base, Derived) { ... }

That's the only thing user code classes need to do. The rest is done in the Serializable proxy base class using compile-time introspection. In a nutshell, what Serializable does is to inject serialize() and deserialize() methods into the class hierarchy. Here's a brief sketch of what it looks like:

	class Serializable(Base, Derived = Base) : Base
	{
		static if (is(Base == Derived)) // this is the base of the hierarchy
		{
			// Base class declarations
			void serialize(...) {
				... // use introspection to extract data members
			}
			void deserialize(...) {
				... // use introspection to reconstitute data members
			}
		}
		else // this is a derived class in the hierarchy
		{
			override void serialize(...) {
				... // use introspection to extract data members
			}
			override void deserialize(...) {
				... // use introspection to reconstitute data members
			}
		}

		// How does the deserializer recreate an instance of
		// Derived? By registering the string name of the class
		// into a global hash:
		static struct Proxy // N.B.: this is instantiated for each Derived class
		{
			// This static this gets instantiated per
			// Derived class, and uses compile-time
			// knowledge about Derived to generate code for
			// reconstructing an instance of Derived.
			static this() {
				deserializers[Derived.stringof] = {
					auto obj = new Derived();
					obj.deserialize(...);
					return obj;
				};
			}
		}
	}

	// This is module-global.
	/*shared*/ static Object delegate(...)[string] deserializers;

	// Global deserialize method that returns an instance of the
	// class hierarchy.
	Object deserialize(...) {
		// Obtain class name from serialized data
		string classname = ...;

		// Dispatch to the correct method registered by Proxy's
		// static this, that recreates the class of the required
		// type.
		return deserializers[classname](...);
	}

The nice thing about this approach is that you have full compile-time information about the target type `Derived`, in both the serialization and deserialization methods. So you can use introspection to automate away most of the boilerplate associated with serialization code. E.g., iterate over __traits(allMembers) to extract data fields, inspect UDAs that allow user classes to specify how the serialization should proceed, etc..


T

-- 
Doubtless it is a good thing to have an open mind, but a truly open mind should be open at both ends, like the food-pipe, with the capacity for excretion as well as absorption. -- Northrop Frye
May 03, 2022
On 3/5/22 16:48, Adam D Ruppe wrote:
> Believe it or not, you don't need to touch the compiler. Open your druntime's object.d and search for `RTInfo`
> 
> http://druntime.dpldocs.info/object.RTInfo.html
> 
> That is instantiated for every user defined type in the program and you have the compile time info..... all druntime uses it for is a tiny bit of GC info and even then only sometimes.
> 
> But it could do so so so much more. Including doing custom factories and runtime reflection buildups!

This looks nice, but I actually meant to allow "template this" in static contexts, as in the bug reports.

I think that might indeed need compiler support? You'll make me happy if that's possible without touching the compiler!
May 03, 2022

On Tuesday, 3 May 2022 at 15:08:53 UTC, H. S. Teoh wrote:

>

class Base : Serializable!(Base) { ... }
class Derived : Serializable!(Base, Derived) { ... }

This is really interesting syntax, I'm surprised that works!

May 03, 2022
On Tue, May 03, 2022 at 04:38:23PM +0000, cc via Digitalmars-d-learn wrote:
> On Tuesday, 3 May 2022 at 15:08:53 UTC, H. S. Teoh wrote:
> > 	class Base : Serializable!(Base) { ... }
> > 	class Derived : Serializable!(Base, Derived) { ... }
> 
> This is really interesting syntax, I'm surprised that works!

https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

It's a bit counterintuitive at first, but once you "get" how it works, it's an extremely powerful technique for leveraging D's compile-time introspection capabilities. And translating compile-time information into runtime using static this(). ;-)


T

-- 
Never step over a puddle, always step around it. Chances are that whatever made it is still dripping.
May 03, 2022
On Tuesday, 3 May 2022 at 16:38:23 UTC, cc wrote:
> This is really interesting syntax, I'm surprised that works!

Can read a little more on my blog about it:

http://dpldocs.info/this-week-in-d/Blog.Posted_2019_06_10.html#tip-of-the-week

pretty cool little pattern.
May 03, 2022

On Tuesday, 3 May 2022 at 16:51:33 UTC, H. S. Teoh wrote:

>

On Tue, May 03, 2022 at 04:38:23PM +0000, cc via Digitalmars-d-learn wrote:

>

On Tuesday, 3 May 2022 at 15:08:53 UTC, H. S. Teoh wrote:

>

class Base : Serializable!(Base) { ... }
class Derived : Serializable!(Base, Derived) { ... }

This is really interesting syntax, I'm surprised that works!

https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

It's a bit counterintuitive at first, but once you "get" how it works, it's an extremely powerful technique for leveraging D's compile-time introspection capabilities. And translating compile-time information into runtime using static this(). ;-)

T

Hm although I am having trouble with that particular implementation:

class Base : Serializable!(Base) {}
class Derived : Serializable!(Base, Derived) {}
class Serializable(Base, Derived = Base) : Base {}
Error: class `test.Base` circular inheritance
Error: template instance `test.Serializable!(Base, Base)` error instantiating
May 03, 2022
On Tue, May 03, 2022 at 04:59:42PM +0000, cc via Digitalmars-d-learn wrote:
> On Tuesday, 3 May 2022 at 16:51:33 UTC, H. S. Teoh wrote:
[...]
> > > On Tuesday, 3 May 2022 at 15:08:53 UTC, H. S. Teoh wrote:
> > > > 	class Base : Serializable!(Base) { ... }
> > > > 	class Derived : Serializable!(Base, Derived) { ... }
[...]
> Hm although I am having trouble with that particular implementation:
> ```d
> class Base : Serializable!(Base) {}
> class Derived : Serializable!(Base, Derived) {}
> class Serializable(Base, Derived = Base) : Base {}
> ```
> ```
> Error: class `test.Base` circular inheritance
> Error: template instance `test.Serializable!(Base, Base)` error
> instantiating
> ```

Oops, sorry, I made a mistake. The definition of Serializable should be:

	class Serializable(Base, Derived = Object) : Base {}

and the corresponding static if in the implementation should test for Object instead of Base.  Basically, it's just some way of differentiating the base class from the derived classes, because you need to declare the serialize methods in the base class without `override` but in the derived classes you need `override`.


T

-- 
Turning your clock 15 minutes ahead won't cure lateness---you're just making time go faster!
May 03, 2022

On Tuesday, 3 May 2022 at 17:05:09 UTC, H. S. Teoh wrote:

>

Oops, sorry, I made a mistake. The definition of Serializable should be:

class Serializable(Base, Derived = Object) : Base {}

There we go, works with this, now I get what it's trying to do:

class Serializable(Base, Derived = Object) : Derived {

What's the purpose of the static struct Proxy? The static this() seems to work without being enclosed in a structure.

May 03, 2022
On Tue, May 03, 2022 at 05:25:06PM +0000, cc via Digitalmars-d-learn wrote:
> On Tuesday, 3 May 2022 at 17:05:09 UTC, H. S. Teoh wrote:
> > Oops, sorry, I made a mistake. The definition of Serializable should be:
> > 
> > 	class Serializable(Base, Derived = Object) : Base {}
> 
> There we go, works with this, now I get what it's trying to do:
> ```d
> class Serializable(Base, Derived = Object) : Derived {
> ```
> 
> What's the purpose of the `static struct Proxy`?  The `static this()` seems to work without being enclosed in a structure.

Actually, come to think of it, Proxy isn't strictly necessary. You could move the static this into Serializable and it'd work. The main purpose here is to create a separate instance of static this() per instantiation of Serializable, i.e., there'd be a separate instance of static this() for each Derived class.

Since the compiler collects all static this()'s into a list of functions that run at program startup, this allows us to initialize the deserializers AA with functions that understand how to create an instance of Derived at runtime.  Since in the scope of the static this we have direct access to compile-time information about Derived, it can use compile-time introspection to inspect Derived and take the appropriate action at runtime based on this compile-time knowledge. In this way, we "translate" compile-time knowledge into runtime knowledge.


T

-- 
PNP = Plug 'N' Pray
1 2
Next ›   Last »