Thread overview
Variant is just a class
Sep 06, 2018
Josphe Brigmo
Sep 06, 2018
Neia Neutuladh
Sep 07, 2018
Josphe Brigmo
Sep 07, 2018
Neia Neutuladh
Sep 07, 2018
Josphe Brigmo
Sep 08, 2018
Neia Neutuladh
Sep 08, 2018
Josphe Brigmo
September 06, 2018
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.










September 06, 2018
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.
September 07, 2018
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.
September 07, 2018
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.
September 07, 2018
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?



September 08, 2018
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.
September 08, 2018
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();



}