www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Mix struct types in the same array

reply tirithen <tirithen gmail.com> writes:
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);
	}
Dec 18 2019
parent reply Sebastiaan Koppe <mail skoppe.eu> writes:
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
Dec 18 2019
parent reply tirithen <tirithen gmail.com> writes:
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!
Dec 18 2019
next sibling parent reply Paul Backus <snarwin gmail.com> writes:
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); }
Dec 18 2019
next sibling parent tirithen <tirithen gmail.com> writes:
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.
Dec 19 2019
prev sibling parent reply tirithen <tirithen gmail.com> writes:
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-4CFAD9B3CA5E5532C5E06 8F9332C781/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.
Dec 19 2019
parent reply Paul Backus <snarwin gmail.com> writes:
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-4CFAD9B3CA5E5532C5E06 8F9332C781/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
Dec 19 2019
parent tirithen <tirithen gmail.com> writes:
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. :)
Dec 20 2019
prev sibling parent Laeeth Isharc <Laeeth laeeth.com> writes:
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 {} */
Dec 20 2019