Thread overview
Mix struct types in the same array
Dec 18, 2019
tirithen
Dec 18, 2019
Sebastiaan Koppe
Dec 18, 2019
tirithen
Dec 19, 2019
Paul Backus
Dec 19, 2019
tirithen
Dec 19, 2019
tirithen
Dec 19, 2019
Paul Backus
Dec 20, 2019
tirithen
Dec 20, 2019
Laeeth Isharc
December 18, 2019
I want to have a array/list/queue of instances of various struct types that represent events. How can I achieve that? With classes I would have interfaces I guess, but I want to use structs if possible as they seem less complex (and faster?).

Can I wrap each struct with in another struct called something like Entry that in turn can contain the event struct some how, or will that just move the problem?

	import std.stdio;

	struct User {
		string username;
		string email;
		string unverifiedEmail;
		string password;
	}

	struct UserCreate {
		string username;
		string email;

		void applyTo(ref User user) {
			user.username = this.username;
			user.unverifiedEmail = this.email;
		}
	}

	struct UserChangePassword {
		string password;

		void applyTo(ref User user) {
			user.password = this.password;
		}
	}

	// Not usable for structs
	interface Event {
		void applyTo(T)(ref T entity);
	}

	void main() {
		// How can I create an array with mixed structs?
		Event[] events;

		events ~= UserCreate("hej", "hej@example.com");
		events ~= UserChangePassword("hased1234");

		User user;
		foreach (event; events) {
			event.applyTo(user);
		}

		writeln(user);
	}
December 18, 2019
On Wednesday, 18 December 2019 at 22:00:20 UTC, tirithen wrote:
> I want to have a array/list/queue of instances of various struct types that represent events. How can I achieve that? With classes I would have interfaces I guess, but I want to use structs if possible as they seem less complex (and faster?).
>
> Can I wrap each struct with in another struct called something like Entry that in turn can contain the event struct some how, or will that just move the problem?

If you know all types up front you can use the Sumtype library on code.dlang.org
December 18, 2019
On Wednesday, 18 December 2019 at 22:11:10 UTC, Sebastiaan Koppe wrote:
>
> If you know all types up front you can use the Sumtype library on code.dlang.org

Thanks, it's a good starting point, the best would be if I only needed to define that the struct would implement void applyTo(ref User user) so that could be run in the loop to update the User entity.

But I'll try the Sumtype library, that takes me forward, thanks for the answer!
December 19, 2019
On Wednesday, 18 December 2019 at 22:17:21 UTC, tirithen wrote:
> On Wednesday, 18 December 2019 at 22:11:10 UTC, Sebastiaan Koppe wrote:
>>
>> If you know all types up front you can use the Sumtype library on code.dlang.org
>
> Thanks, it's a good starting point, the best would be if I only needed to define that the struct would implement void applyTo(ref User user) so that could be run in the loop to update the User entity.

If all of the structs need to implement a particular method (or set of methods), you can use an interface and a templated adapter class to give them a common type. Here's a simple example:

import std.stdio: writeln;

struct User
{
    string name;
}

interface Action
{
    void applyTo(ref User);
}

class ActionAdapter(T) : Action
{
    T payload;

    this(T payload)
    {
        this.payload = payload;
    }

    override void applyTo(ref User user)
    {
        payload.applyTo(user);
    }
}

Action action(T)(T payload)
{
    return new ActionAdapter!T(payload);
}

struct SayHello
{
    void applyTo(ref User user)
    {
        writeln("Hello, ", user.name, ".");
    }
}

struct SayGoodbye
{
    void applyTo(ref User user)
    {
        writeln("Goodbye, ", user.name, ".");
    }
}

void main()
{
    Action[] actions = [action(SayHello()), action(SayGoodbye())];
    auto user = User("Joe Schmoe");

    foreach (action; actions)
        action.applyTo(user);
}
December 19, 2019
On Thursday, 19 December 2019 at 00:00:56 UTC, Paul Backus wrote:
> If all of the structs need to implement a particular method (or set of methods), you can use an interface and a templated adapter class to give them a common type. Here's a simple example:
>
Thank you for the example that looks like a wonderful example, I'll try it out. I like that the structs then can be declared when the array is constructed.
December 19, 2019
On Thursday, 19 December 2019 at 00:00:56 UTC, Paul Backus wrote:
>
> interface Action
> {
>     void applyTo(ref User); <-- change to applyTo(T)(ref T)
> }
>
> class ActionAdapter(T) : Action
> {
>     T payload;
>
>     this(T payload)
>     {
>         this.payload = payload;
>     }
>
>     override void applyTo(ref User user) <-- change to applyTo(U)(ref U entity)
>     {
>         payload.applyTo(user);
>     }
> }

Is there a way I can make the example a bit more generic even by replacing the User type with a template. I tried:

applyTo(ref User); <-- applyTo(T)(ref T)
applyTo(ref User user) <-- applyTo(U)(ref U entity)

It compiled but crashed with:

Linking...
/usr/bin/ld: .dub/build/application-debug-linux.posix-x86_64-dmd_2089-4CFAD9B3CA5E5532C5E0608F9332C781/tryit.o: in function `_Dmain':
.....tryit/source/app.d:55: undefined reference to `_D5plant5tryit3app5Event__T7applyToTSQBjQBgQBd4UserZQzMFKQvZv'
collect2: fel: ld returnerade avslutningsstatus 1

I'm still a bit new to this language so need to practice templates more, but I find D overall nice.
December 19, 2019
On Thursday, 19 December 2019 at 18:21:32 UTC, tirithen wrote:
> On Thursday, 19 December 2019 at 00:00:56 UTC, Paul Backus wrote:
>>
>> interface Action
>> {
>>     void applyTo(ref User); <-- change to applyTo(T)(ref T)
>> }
>>
>> class ActionAdapter(T) : Action
>> {
>>     T payload;
>>
>>     this(T payload)
>>     {
>>         this.payload = payload;
>>     }
>>
>>     override void applyTo(ref User user) <-- change to applyTo(U)(ref U entity)
>>     {
>>         payload.applyTo(user);
>>     }
>> }
>
> Is there a way I can make the example a bit more generic even by replacing the User type with a template. I tried:
>
> applyTo(ref User); <-- applyTo(T)(ref T)
> applyTo(ref User user) <-- applyTo(U)(ref U entity)
>
> It compiled but crashed with:
>
> Linking...
> /usr/bin/ld: .dub/build/application-debug-linux.posix-x86_64-dmd_2089-4CFAD9B3CA5E5532C5E0608F9332C781/tryit.o: in function `_Dmain':
> .....tryit/source/app.d:55: undefined reference to `_D5plant5tryit3app5Event__T7applyToTSQBjQBgQBd4UserZQzMFKQvZv'
> collect2: fel: ld returnerade avslutningsstatus 1
>
> I'm still a bit new to this language so need to practice templates more, but I find D overall nice.

The reason this doesn't work is that interface methods are virtual, and virtual methods can't be templates. [1] If you want to have multiple kinds of entities, one option is to create an Entity interface and EntityAdapter(T) class using the same technique as above, and have your applyTo method accept an Entity as its argument. You can see an example of what that would look like at the following link:

https://gist.github.com/pbackus/f61734804a627d379c7916f783ea7725#file-oop_version-d

Another option is to forget about classes and interfaces and do everything with templates, using a library like sumtype [2]. Here's a link to an example of what that would look like:

https://gist.github.com/pbackus/f61734804a627d379c7916f783ea7725#file-template_version-d

Advantages of the interface + adapter version:
  + Can easily add new Actions and Entities, without changing existing code
  + Method dispatch is handled automatically by the compiler

Advantages of sumtype + templates version:
  + No need to allocate adapter objects on the heap

[1] https://dlang.org/spec/function.html#virtual-functions
[2] https://code.dlang.org/packages/sumtype
December 20, 2019
On Thursday, 19 December 2019 at 21:26:51 UTC, Paul Backus wrote:
>
> The reason this doesn't work is that interface methods are virtual, and virtual methods can't be templates. [1] If you want to have multiple kinds of entities, one option is to create an Entity interface and EntityAdapter(T) class using the same technique as above, and have your applyTo method accept an Entity as its argument. You can see an example of what that would look like at the following link:
>
> https://gist.github.com/pbackus/f61734804a627d379c7916f783ea7725#file-oop_version-d
>
> Another option is to forget about classes and interfaces and do everything with templates, using a library like sumtype [2]. Here's a link to an example of what that would look like:
>
> https://gist.github.com/pbackus/f61734804a627d379c7916f783ea7725#file-template_version-d

Thanks for a good explanation around virtual methods and templates as well as examples! Now I have plenty of good examples to play around with. :)
December 20, 2019
On Wednesday, 18 December 2019 at 22:17:21 UTC, tirithen wrote:
> On Wednesday, 18 December 2019 at 22:11:10 UTC, Sebastiaan Koppe wrote:
>>
>> If you know all types up front you can use the Sumtype library on code.dlang.org
>
> Thanks, it's a good starting point, the best would be if I only needed to define that the struct would implement void applyTo(ref User user) so that could be run in the loop to update the User entity.
>
> But I'll try the Sumtype library, that takes me forward, thanks for the answer!

https://github.com/atilaneves/concepts

interface IFoo {
    int foo(int i, string s) @safe;
    double lefoo(string s) @safe;
}

@implements!(Foo, IFoo)
struct Foo {
    int foo(int i, string s) @safe { return 0; }
    double lefoo(string s) @safe { return 0; }
}

// doesn't compile
/*
@implements!(Oops, IFoo)
struct Oops {}
*/