December 04
On 04/12/2024 8:33 PM, Walter Bright wrote:
> On 12/3/2024 6:39 PM, Timon Gehr wrote:
>> Sure, but the question was, how do you do actually do the serialization via introspection.
> 
> As I haven't attempted that, I might suggest use of:
> 
> 1. interfaces
> 
> 2. output ranges (sinks)

Don't forget template this parameter!

(its horrible)

December 04

On Wednesday, 4 December 2024 at 02:39:12 UTC, Timon Gehr wrote:

>

Sure, but the question was, how do you do actually do the serialization via introspection.

C# does custom serialization by inheritance of ISerializable interface. There's also aspect-oriented approach, but it's deprecated in favor of DTO pattern.

December 04
void serialize(byte[] o, string s){}

struct MyDto
{
	struct SerializerAspect
	{
		static void serialize(byte[] o, MyDto s){}
	}
}

void serialize2(T)(byte[] o, T s) if(!is(T.SerializerAspect))
{
	serialize(o,s);
}

void serialize2(T)(byte[] o, T s) if(is(T.SerializerAspect))
{
	T.SerializerAspect.serialize(o,s);
}

void test()
{
	byte[] o;
	string a;
	serialize2(o,a);
	MyDto c;
	serialize2(o,c);
}
December 05
On Wed, 4 Dec 2024 at 08:16, Walter Bright via Digitalmars-d < digitalmars-d@puremagic.com> wrote:

> Koenig lookup? I ran away, far away, from that, after implementing it for C++.
>
> It's a nightmare.
>

So what do you suggest?
UFCS makes this pattern more attractive than ever, but our lookup rules
inhibit any practical application.
The problem is essentially that a more-local import or singular declaration
shadows an entire overload set from a less-local scope, rather than merging
with it.


December 05
On Wed, 4 Dec 2024 at 12:56, Timon Gehr via Digitalmars-d < digitalmars-d@puremagic.com> wrote:

> On 12/3/24 12:55, Manu wrote:
> > Maybe someone has a pattern for doing this kind of thing...
>
> There are a couple solutions that look okay already, but basically, importing into any unordered scope works, e.g.
>
> ```d
> module default_serialise;
> ubyte[] serialise(T)(T arg)if(!is(T==struct)){ return [1]; }
> ```
>
> ```d
> module user_code;
> struct S{}
> ubyte[] serialise(S){ return [2]; }
> ```
>
> ```d
> import default_serialise;
>
> void main(){
>      import std;
>      static struct Dummy{
>          import default_serialise: serialise;
>          import user_code: serialise;
>      }
>      import user_code;
>      writeln(Dummy.serialise(1)); // [1]
>      writeln(Dummy.serialise(S())); // [2]
> }
> ```
>
> You can also use a dummy template scope (template Dummy(), and then
> `Dummy!().serialise`).
>
> I dislike function-local import semantics quite a bit as they do not follow the well-thought-out overloading rules that apply to other imports.
>

"Unordered scope"; it never occurred to me that was a concept! But I can
see that now...
What a horrible hack, but I guess that could work.
I'll see if I can do anything with this idea...


December 05
On Wed, 4 Dec 2024 at 17:31, Walter Bright via Digitalmars-d < digitalmars-d@puremagic.com> wrote:

> Back before I ended work on C++, a number of C++ Illuminati told me
> privately
> that ADL was a mistake. Things may have changed since then, and I don't
> know
> about that.
>

I've used it effectively for decades and never had any problems.
I'm not advocating ADL necessarily, I can see the complexity; but we need
an effective response though. D has made the patterns it supports somewhat
more attractive than they are in C++ (mainly thanks to UFCS), yet the local
imports shadowing overload sets issue is confusing and unintuitive, and
makes the situation fail completely.

For my money, SFINAE is the most egregious bit of the C++ spec. (though we're not doing a whole lot better!)


December 05
On Wednesday, December 4, 2024 7:31:50 PM MST Manu via Digitalmars-d wrote:
> On Wed, 4 Dec 2024 at 08:16, Walter Bright via Digitalmars-d <
>
> digitalmars-d@puremagic.com> wrote:
> > Koenig lookup? I ran away, far away, from that, after implementing it for C++.
> >
> > It's a nightmare.
>
> So what do you suggest?
> UFCS makes this pattern more attractive than ever, but our lookup rules
> inhibit any practical application.
> The problem is essentially that a more-local import or singular declaration
> shadows an entire overload set from a less-local scope, rather than merging
> with it.

The way that I've seen serialization done in D is to use UDAs, and the (de)serializer uses type introspection to look at the UDAs to see what to do. It could handle certain types by default (e.g. the built in language types) and then extrapolate what to do for a type that is made up of known types but isn't marked up with UDAs - or it could require that any types be marked up to be (de)serialized. Either way, when dealing with a type that doesn't have a UDA, the variable declaration using that type could use a UDA to provide the required functions - or it could provide an intermediate type (be it one that has a serialize and deseralize function that handles the variable's type or because it's a type that that variable can be converted to and from that the (de)serializer knows how to handle). IIRC, the seralization stuff I saw in Java years ago did something similar (though obviously with runtime reflection). Though obviously, C++ isn't going to use that approach unless they've added some form of UDAs in a recent version, and I missed it.

Obviously, that's a very different approach than what you're talking about, and it may or may not fit what you're ultimately looking to do, but it's what I've seen done - e.g. Mir has a serialization solution along those lines.

- Jonathan M Davis



December 05
On Thursday, 5 December 2024 at 13:16:06 UTC, Jonathan M Davis wrote:
> IIRC, the seralization stuff I saw in Java years ago did something similar (though obviously with runtime reflection). Though obviously, C++ isn't going to use that approach unless they've added some form of UDAs in a recent version, and I missed it.

Java serialisation libs still do employ annotations(UDA) for customising serialised output, and imho is much, much better than C++ approach.

December 05
On Tuesday, 3 December 2024 at 19:34:21 UTC, Alexandru Ermicioi wrote:
> On Tuesday, 3 December 2024 at 11:55:50 UTC, Manu wrote:
>> Has anyone ever experimented with a pattern like this? Essentially, I can't work out how to expand /combine an overload set to include symbols from multiple imports....
>
> as a compromise you could have an uda that packs custom serialisation defined on type itself or field of that type, then you can make serializer employ it when it sees it somewhere.

That is essentially what Go does for its serialisation (Marshal and Unmarshal) routines.

Only the public fields of a struct can be processed, and how to handle a field in a non default fashion is marked by an attribute - an extra string.

```Go
type DelSite struct {
    SiteID types.SiteID `json:"site-id" validate:"min=1"`
}
```

One of those is then encoded as follows, where 's' happens to be a struct ptr:

```Go
    var b []byte
    b, err := json.Marshal(s)
```

It is using run time reflection, but I see no obvious reason why compile time reflection could not be used instead, since that is what D has.

December 06
On 06/12/2024 8:07 AM, Derek Fawcus wrote:
> It is using run time reflection, but I see no obvious reason why compile time reflection could not be used instead, since that is what D has.

I said this over in ideas, but the problem isn't controlling serialization, but the entry point into it.

Ideal:

```d
class Parent {
	void serialize() {
		// An implementation based upon typeof(this) goes here,
		//  currently this could only be Parent
	}
}

class Child : Parent {
}
```

How it has to be implemented today to work:


```d
class Parent {
	abstract void serialize();

	protected void serialize_(T:Parent)(T self) {
		
	}
}

class Child : Parent {
	void serialize() {
		super.serialize_(this);
	}
}
```

If you are forcing users of a parent class to add the call explicitly into the parent, its a major pain that other languages simply do not have. Not to mention easily forgotten.

This is what my attribute that I said I'm thinking about ``@reinterptAsChild`` would solve.