www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Variant is just a class

reply Josphe Brigmo <JospheBrigmo gmail.com> writes:
Variants can hold an arbitrary set of types.

I imagine that it is effectively just a type id and an object 
pointer!?

If so, then it really is just a special type of a class class.

Let me explain:

I have a class that will "hold/wrap" another class.

I could hold them using a variant but also I could require some 
new wrapper class that takes the object and include that wrapper 
class instead:

class A
{
    variant o;
}

vs

class A
{
    B oo;
}

class B
{
    ...
    Object ooo; // or variant
}


id matching on o is basically virtual functions. The matcher 
dispatching is single dispatch.


Of course, the class version gives compile time specificity since 
it provides a compile time interface that the variant does not 
represent.

It seems that variant and oop are essentially the same thing, 
more or less, as whatever can be done in one can effectively be 
done in the other, except, of course, that the class version has 
compile time type information associated with it, which sort of 
restricts variant to a subset of all types!?!

But variant can reduce the code complexity if one restricts it's 
inputs to a specific class of types:


struct VariantClass(T...)
{
     private Variant v;
     alias v this;
	static foreach(t; T)
		auto opAssign(t x)
		{
			v = x;
		}


}



Then VariantClass will prevent any arbitrary type from being 
assigned to the variant, effectively allow inheritance to be 
used(in the sense that it will prevent any type from being used 
at compile time, like inheritance):

VariantClass!X v; // only T : X's are allowed.


Matching then is dispatch. We could further extend VariantClass 
to return specific classes for each type that dispatch to match 
and vice versa.

They might not be exactly the same though but it seems that they 
essentially both cover the same problem but from different 
perspectives.

Anyone have any thoughts? I'm mainly trying to decide if I should 
use a variant or go full oop. Variant seems more appropriate than 
using Object for a general purpose singleton container in that I 
can leverage it's design. If I use oop then it does require me to 
design an inheritance between the objects.

It would be cool if duck typing could be used though.

Essentially all I want to do is access a very small interface of 
the object that has a draw command and maybe a few other things. 
I want all the objects to posses these functions(be a duck) but 
other than that, I could care less.

Seems like I could extend the VariantClass to do introspection on 
the types and make sure they have the members I need but 
otherwise do not limit their types to inheritance but then it 
starts feeling like oop as I'll need to specify an interface.

Seems like partial interface matching is what is needed. One 
specifies an interface I and any object that partially matches it 
by design(contains same function signatures, regardless if it 
physically implements the interface).

Can D project interfaces like this?

interface I
{
    int foo(int);
}

I i = project!I(o);

project returns part of the interface o(can be object but must 
have a int foo(int)) that matches I or null otherwise.

Seems though this cannot be used at runtime though since function 
signatures are not transported in the binary? If they are, then 
maybe it would work and would reduce the overhead of oop as one 
could just project types to other types that overlap.
Sep 06 2018
parent reply Neia Neutuladh <neia ikeran.org> writes:
On Thursday, 6 September 2018 at 10:18:43 UTC, Josphe Brigmo 
wrote:
 Variants can hold an arbitrary set of types.

 I imagine that it is effectively just a type id and an object 
 pointer!?
It's a typeid and a static array large enough to hold any basic builtin type: the now-deprecated creal, a dynamic array, or a delegate. If you make a Variant from an object, it stores that object reference. The object reference is just a pointer, yes. If you make a Variant from a 256-byte struct, it copies that struct onto the heap and stores a pointer. If you make a Variant from a 6-byte struct, then it stores that struct and does no heap allocations.
 If so, then it really is just a special type of a class class.
It's similar to a java.lang.Object with explicit boxing, but without the need to create a new wrapper class for each value type.
 It seems that variant and oop are essentially the same thing, 
 more or less, as whatever can be done in one can effectively be 
 done in the other, except, of course, that the class version 
 has compile time type information associated with it, which 
 sort of restricts variant to a subset of all types!?!
Object-oriented programming includes inheritance and member function overloading. Variant doesn't; it's just about storage. If you're working with classes, you'd be better off using a base class or interface instead of Variant for fields that can only hold objects of those types.
 But variant can reduce the code complexity if one restricts 
 it's inputs to a specific class of types:
Yes, for which you can use std.variant.Algebraic. For instance, Algebraic!(int, long, float) will accept ints, longs, and floats, but nothing else.
 Then VariantClass will prevent any arbitrary type from being 
 assigned to the variant, effectively allow inheritance to be 
 used(in the sense that it will prevent any type from being used 
 at compile time, like inheritance):

 VariantClass!X v; // only T : X's are allowed.
That's equivalent to `X v;` except with a wrapper around it.
 Matching then is dispatch. We could further extend VariantClass 
 to return specific classes for each type that dispatch to match 
 and vice versa.
I think you're saying that this sort of Algebraic could expose any methods and fields common to all its types?
 Can D project interfaces like this?

 interface I
 {
    int foo(int);
 }

 I i = project!I(o);
You can write code to make that work. It would create a wrapper class that implements the requested interface, and that wrapper would just forward everything to the wrapped value. It would be a lot easier just to have the type implement the interface itself, if that's possible.
 Seems though this cannot be used at runtime though since 
 function signatures are not transported in the binary? If they 
 are, then maybe it would work and would reduce the overhead of 
 oop as one could just project types to other types that overlap.
You can use the witchcraft library on dub to perform runtime introspection, but there would be a performance penalty.
Sep 06 2018
parent reply Josphe Brigmo <JospheBrigmo gmail.com> writes:
On Thursday, 6 September 2018 at 20:25:18 UTC, Neia Neutuladh 
wrote:
 On Thursday, 6 September 2018 at 10:18:43 UTC, Josphe Brigmo 
 wrote:
 Variants can hold an arbitrary set of types.

 I imagine that it is effectively just a type id and an object 
 pointer!?
It's a typeid and a static array large enough to hold any basic builtin type: the now-deprecated creal, a dynamic array, or a delegate. If you make a Variant from an object, it stores that object reference. The object reference is just a pointer, yes. If you make a Variant from a 256-byte struct, it copies that struct onto the heap and stores a pointer. If you make a Variant from a 6-byte struct, then it stores that struct and does no heap allocations.
 If so, then it really is just a special type of a class class.
It's similar to a java.lang.Object with explicit boxing, but without the need to create a new wrapper class for each value type.
 It seems that variant and oop are essentially the same thing, 
 more or less, as whatever can be done in one can effectively 
 be done in the other, except, of course, that the class 
 version has compile time type information associated with it, 
 which sort of restricts variant to a subset of all types!?!
Object-oriented programming includes inheritance and member function overloading. Variant doesn't; it's just about storage.
We are talking about two different things that are related: A variant holds a set of objects. Using VariantClass limits the types to a subset and allows for inherited types to be added. Those objects may be classes which already have inheritance and hence matching and calling their methods will dispatch appropriately. A variant sits on top of the object hierarchy, it is not somewhere in the middle where objects will inherit from it(which is impossible). The difference is simply Object x; Variant y; There is very little difference. If x is a class type then so will variant hold class type and it will act just like the Object does. That is, Variant can do no worse than just being an object(except it then becomes pointless as it can hold only one type.
 If you're working with classes, you'd be better off using a 
 base class or interface instead of Variant for fields that can 
 only hold objects of those types.

 But variant can reduce the code complexity if one restricts 
 it's inputs to a specific class of types:
Yes, for which you can use std.variant.Algebraic. For instance, Algebraic!(int, long, float) will accept ints, longs, and floats, but nothing else.
It is not the same since Algebraic does not allow inherited types inside it's container? Or maybe it does? VariantClass!X will accept anything derived from X and it will dispatch appropriately since it just delegates to the normal class dispatching mechanisms.
 Then VariantClass will prevent any arbitrary type from being 
 assigned to the variant, effectively allow inheritance to be 
 used(in the sense that it will prevent any type from being 
 used at compile time, like inheritance):

 VariantClass!X v; // only T : X's are allowed.
That's equivalent to `X v;` except with a wrapper around it.
Yes, but the whole point of the wrapper is simply to insure that only a subset of types is used but allow for different types. X v; only allows types derived from X. VariantClass!(X, Y) v; allows types derived from X or from Y. then matching on the type will simply delegate everything appropriately. If X and Y have a common type I then one can do VariantClass!I v; which would be the same as I v; But not the same as Algebraic!I v; because we couldn't stick in a derived object for I. We can cast, and it works but it simply doesn't naturally allow derived types for some reason. VariantClass allows derived types. This is a big difference because Algebraic doesn't naturally work well with oop but VariantClass does.
 Matching then is dispatch. We could further extend 
 VariantClass to return specific classes for each type that 
 dispatch to match and vice versa.
I think you're saying that this sort of Algebraic could expose any methods and fields common to all its types?
 Can D project interfaces like this?

 interface I
 {
    int foo(int);
 }

 I i = project!I(o);
You can write code to make that work. It would create a wrapper class that implements the requested interface, and that wrapper would just forward everything to the wrapped value. It would be a lot easier just to have the type implement the interface itself, if that's possible.
But this is just oop. Opp requires more work to design because one has to implement the interfaces in a prescribed way. What I am talking about taking any object and if it has certain methods that conform to some "interface" then it will behave as if it were derived... even if it were not specified as derived. Why this is better is because it allows objects that may not have been inherited from some interface in the design(pre-existing). interface I { int foo(int); } class A { int foo(int); } DuckingVariant!I v; A a; v = a; then A, for all practical purposes is an I and v.foo can be called. DuckingVariant simply checks if the type can be projected on to I, which it can in this case. Now, if A has meta info but no way to change the source, then DuckingVariant could simply check if it matches the specified interfaces(s) and if it does, do the mapping(which is just forwarding). Of course, if A changes the name of foo the everything will break and since A doesn't implement I it is more prone to happen... Also, such meta info isn't generally included in the binaries so there is really no way to use DuckingVariant in general with arbitrary code. Remember, the idea is simply to wrap an object that has some simple functionality needed such as implementing a few functions. Seems using oop is overkill when it is not needed. Compile time DbI would work fine but it doesn't work for runtime. So DuckingVariant would sort of bridge the two by making DbI work at run time, assuming it could peak at the meta info of the type. If one starts getting to fancy then basically one is just implementing COM with queryinterface and such.
 Seems though this cannot be used at runtime though since 
 function signatures are not transported in the binary? If they 
 are, then maybe it would work and would reduce the overhead of 
 oop as one could just project types to other types that 
 overlap.
You can use the witchcraft library on dub to perform runtime introspection, but there would be a performance penalty.
Cool, I will check it out. It seems it would allow DuckingVariant to be created.
Sep 06 2018
parent reply Neia Neutuladh <neia ikeran.org> writes:
On Friday, 7 September 2018 at 03:04:19 UTC, Josphe Brigmo wrote:
 We are talking about two different things that are related:

 A variant holds a set of objects. Using VariantClass limits the 
 types to a subset and allows for inherited types to be added.
Algebraic!SomeInterface should allow anything that inherits from that interface (possibly with an explicit cast).
 Why this is better is because it allows objects that may not 
 have been inherited from some interface in the 
 design(pre-existing).
OOP is better when you don't trust people to implement types correctly and you want them to get errors at the proper location when they get it wrong. Structural typing is better when you don't trust people to implement types correctly and you want to make things easier on yourself when they don't. They're both useful. Most of the time, you're dealing with your own types. Sometimes, you need a type you don't control to implement an interface. the default way of making structural typing work in D is with templates. You can also auto-generate a wrapper type.
 Cool, I will check it out. It seems it would allow 
 DuckingVariant to be created.
It would allow you to make a wrapper type that works with a wide range of types. It's probably worse on average than writing a template to create a wrapper class that implements the interface you need. I mean, the wrapper is pretty much (untested): class Wrapper(Interface, Wrapped): Interface { Wrapped wrapped; static foreach (member; __traits(allMembers, Interface)) { static foreach (overload; __traits(getOverloads, Interface, member)) { mixin(`ReturnType!Overload ` ~ method ~ `(Parameters!overload params)` { return wrapped.` ~ method ~ `(params); }`); } } } And now you don't need a VariantClass!SomeInterface, just SomeInterface.
Sep 07 2018
parent reply Josphe Brigmo <JospheBrigmo gmail.com> writes:
On Friday, 7 September 2018 at 18:18:50 UTC, Neia Neutuladh wrote:
 On Friday, 7 September 2018 at 03:04:19 UTC, Josphe Brigmo 
 wrote:
 We are talking about two different things that are related:

 A variant holds a set of objects. Using VariantClass limits 
 the types to a subset and allows for inherited types to be 
 added.
Algebraic!SomeInterface should allow anything that inherits from that interface (possibly with an explicit cast).
It doesn't. import std.stdio, std.variant; class A { } class B : A { } void main() { B b; Algebraic!A a; a = b; } "Cannot store a B in a VariantN!(8LU, A). Valid types are (A)"
 Why this is better is because it allows objects that may not 
 have been inherited from some interface in the 
 design(pre-existing).
OOP is better when you don't trust people to implement types correctly and you want them to get errors at the proper location when they get it wrong. Structural typing is better when you don't trust people to implement types correctly and you want to make things easier on yourself when they don't. They're both useful. Most of the time, you're dealing with your own types. Sometimes, you need a type you don't control to implement an interface. the default way of making structural typing work in D is with templates. You can also auto-generate a wrapper type.
 Cool, I will check it out. It seems it would allow 
 DuckingVariant to be created.
It would allow you to make a wrapper type that works with a wide range of types. It's probably worse on average than writing a template to create a wrapper class that implements the interface you need. I mean, the wrapper is pretty much (untested): class Wrapper(Interface, Wrapped): Interface { Wrapped wrapped; static foreach (member; __traits(allMembers, Interface)) { static foreach (overload; __traits(getOverloads, Interface, member)) { mixin(`ReturnType!Overload ` ~ method ~ `(Parameters!overload params)` { return wrapped.` ~ method ~ `(params); }`); } } } And now you don't need a VariantClass!SomeInterface, just SomeInterface.
One of the problems with the above is that it doesn't allow different 'Wrapped' to equate to the same type: Wrapper!(I, A) != Wrapper!(I, B) Although, by construction, they implement I e.g., one couldn't make a variable Wrapped!(I, A) a; and put a Wrapped!(I,B) in it because D will think they are different types simply because the parameters are different. I guess one needs to return a WrappedBase!I type that is common to all and does some magic to trick the compiler. Although, maybe Wrapped!(I, Object) a; will allow this to work?
Sep 07 2018
parent reply Neia Neutuladh <neia ikeran.org> writes:
On Friday, 7 September 2018 at 23:37:05 UTC, Josphe Brigmo wrote:
 On Friday, 7 September 2018 at 18:18:50 UTC, Neia Neutuladh
 Algebraic!SomeInterface should allow anything that inherits 
 from that interface (possibly with an explicit cast).
It doesn't.
I *did* say "possibly with an explicit cast", and adding an explicit cast does work.
 One of the problems with the above is that it doesn't allow 
 different 'Wrapped' to equate to the same type:

 Wrapper!(I, A) != Wrapper!(I, B)

 Although, by construction, they implement I
That's the whole point. You can use this to wrap a struct or object, and then you can use it with anything that takes an `I`.
 I guess one needs to return a WrappedBase!I type that is common 
 to all and does some magic to trick the compiler.
You can have variables and fields whose type is an interface, so just use the interface. If you must, it's pretty trivial to write an abstract base class for the Wrapper class I wrote above.
 Although, maybe

 Wrapped!(I, Object) a;

 will allow this to work?
That would only work if class Object conforms to that interface.
Sep 07 2018
parent Josphe Brigmo <JospheBrigmo gmail.com> writes:
Here is a working example:

import std.stdio;

class Project(Wrapped, Interface) : Interface
{
	import std.traits;
	Wrapped wrapped;

	static foreach (member; __traits(allMembers, Interface))
	{
		static foreach (overload; __traits(getOverloads, Interface, 
member))
		{			
			mixin(`ReturnType!overload ` ~ member ~ `(Parameters!overload 
params) { return wrapped.` ~ member ~ `(params); }`);
		}
	}

	//this(Wrapped w)
	static Interface opCall(Wrapped w)
	{
		auto t = new typeof(this);
		t.wrapped = w;
		return t;
	}
}



interface I
{
     void foo();
}

class A
{
     void foo() { writeln("A"); }
}

class B
{
     void foo() { writeln("B"); }
}

void main()
{
	auto b = new B();
	auto a = new A();
	auto x = Project!(A, I)(a);
	auto y = Project!(B, I)(b);


	a.foo();
	b.foo();
	x.foo();
	y.foo();



}
Sep 07 2018