Thread overview
[phobos] phobos commit, revision 1737
Jul 08, 2010
dsource.org
Jul 08, 2010
Shin Fujishiro
Jul 08, 2010
Michel Fortin
Jul 08, 2010
Shin Fujishiro
[phobos] Design question - is return type Algebraic?
Jul 09, 2010
Shin Fujishiro
July 08, 2010
phobos commit, revision 1737


user: rsinfu

msg:
[devel] std.typecons.Any for ad-hoc polymorphism on duck-typed objects

http://www.dsource.org/projects/phobos/changeset/1737

July 08, 2010
Hi Shin,


This is solid work! There's an opportunity here - it overlaps a LOT with std.variant.Algebraic.

Essentially it would be best if you integrated opDispatch, _onActiveDuck, and friends inside Algebraic. Then Algebraic would become meta-duck - it still supports any combination of types, but you can call any method they share against the Algebraic.

What do you think?


Andrei

P.S. I *love* the trick with the homonym inner namespace! It solves the naming problem so elegantly. I'll use it in RefCounted too.

On 07/08/2010 05:07 AM, dsource.org wrote:
> phobos commit, revision 1737
>
>
> user: rsinfu
>
> msg:
> [devel] std.typecons.Any for ad-hoc polymorphism on duck-typed objects
>
> http://www.dsource.org/projects/phobos/changeset/1737
>
> _______________________________________________
> phobos mailing list
> phobos at puremagic.com
> http://lists.puremagic.com/mailman/listinfo/phobos
July 08, 2010
Andrei Alexandrescu <andrei at erdani.com> wrote:
> Hi Shin,
> 
> 
> This is solid work! There's an opportunity here - it overlaps a LOT with std.variant.Algebraic.
> 
> Essentially it would be best if you integrated opDispatch, _onActiveDuck, and friends inside Algebraic. Then Algebraic would become meta-duck - it still supports any combination of types, but you can call any method they share against the Algebraic.
> 
> What do you think?
> 
> 
> Andrei

I too think that these features should be in Algebraic.

Actually, I created Any because Algebraic did not support dispatching and copy constructing.  I did not touch Algebraic because it was rather difficult to implement opDispatch to the backing VariantN.

Then, may I integrate Any into Algebraic?  The interface will be changed:

Algebraic!(short, int) x;
x.allowed!int	-->	x.Algebraic.allowed!int
x.hasValue	-->	x.Algebraic.empty
x.peek!int	-->	&(x.Algebraic.instance!int())


> P.S. I *love* the trick with the homonym inner namespace! It solves the naming problem so elegantly. I'll use it in RefCounted too.

It was a bit surprising for me that the inner template could access member variables without being mixed in.  But I found that it's just another "template member function", and so it's legal. :-)


Shin
July 08, 2010
Le 2010-07-08 ? 8:13, Andrei Alexandrescu a ?crit :

> This is solid work! There's an opportunity here - it overlaps a LOT with std.variant.Algebraic.


Indeed. It's great. But why is the protection level set to 'package'? Isn't it meant to be used outside of Phobos?


> Essentially it would be best if you integrated opDispatch, _onActiveDuck, and friends inside Algebraic. Then Algebraic would become meta-duck - it still supports any combination of types, but you can call any method they share against the Algebraic.

One thing I've missed about Algebraic is the ability to switch on the type, somewhat like this:

	Algebraic!(A, B, C) x;
	switch (x.type) {
		case x.typecode!A:
		case x.typecode!B:
		case x.typecode!C:
			...
	}

Seems like this would be easy by switching on Any's _duckID, but this facility isn't exposed (it's all private).

Also, this approach using _duckID is a big difference from VariantN (and its derivative Algebraic), which stores a pointer to the typeid to identify the type instead of using a compile-time constant. I think the compile time constant is better since beside being switch-friendly it makes optimizations easier for the compiler.

So I think it'd be better to simply replace Algebraic's implementation with the one from Any. Or perhaps Algebraic and VariantN could be retrofitted so it can use a constant instead of a typeid pointer, but I'm not sure how it'd work.


> P.S. I *love* the trick with the homonym inner namespace! It solves the naming problem so elegantly. I'll use it in RefCounted too.

It's a nice trick and a good application of it.

In fact, I remember myself proposing something similar to allow properties with overloaded operators a few months ago when the property debate was raging. :-)

-- 
Michel Fortin
michel.fortin at michelf.com
http://michelf.com/



July 09, 2010
Michel Fortin <michel.fortin at michelf.com> wrote:
> Le 2010-07-08 ? 8:13, Andrei Alexandrescu a ?crit :
> 
> > This is solid work! There's an opportunity here - it overlaps a LOT with std.variant.Algebraic.
> 
> 
> Indeed. It's great. But why is the protection level set to 'package'? Isn't it meant to be used outside of Phobos?

I needed it to implement native codeset support in stdio.  I just went conservative for adding a public stuff that people might not use...


> One thing I've missed about Algebraic is the ability to switch on the type, somewhat like this:
> 
> 	Algebraic!(A, B, C) x;
> 	switch (x.type) {
> 		case x.typecode!A:
> 		case x.typecode!B:
> 		case x.typecode!C:
> 			...
> 	}
> 
> Seems like this would be easy by switching on Any's _duckID, but this facility isn't exposed (it's all private).

It's nice.  I'll add it!

switch (x.Algebraic.type) {
    case x.Algebraic.typeCode!A:
    case x.Algebraic.typeCode!B:
   ...
    default:
}


Shin
July 08, 2010
On 07/08/2010 09:28 AM, Shin Fujishiro wrote:
> I too think that these features should be in Algebraic.

Excellent!

> Actually, I created Any because Algebraic did not support dispatching and copy constructing.  I did not touch Algebraic because it was rather difficult to implement opDispatch to the backing VariantN.
>
> Then, may I integrate Any into Algebraic?  The interface will be changed:
>
> Algebraic!(short, int) x;
> x.allowed!int	-->	x.Algebraic.allowed!int
> x.hasValue	-->	x.Algebraic.empty
> x.peek!int	-->	&(x.Algebraic.instance!int())

Yes, absolutely. Your design is much better because it avoids hiding e.g. "allowed" in some hosted type with its own "allowed". This will become, I suspect, a gold standard for D types that define opDispatch or alias this.

As you sure have noticed and as Michel pointed out, putting your stuff into Algebraic might be a bit difficult. The design is simple, but the implementation is quite hard to follow and relies on pointer to implementation instead of index into implementation table. (Variant must use pointer to implementation because it supports unbounded types.)

If you find it necessary to completely divorce the Variant implementation from the Algebraic implementation, please do so.

Finally, a suggestion: I have hoped to find time to define opDispatch for Variant. It should accept template variadics and dynamically dispatch the call to the supported type if compatible, or throw an exception otherwise. It's the dynamic counterpart of your opDispatch.

Example:

class A { double foo(int x) { return 1.0 / x; } }
auto v = Variant(new A);
Variant result = v.foo(2);
assert(result.Variant.get!double() == 0.5);

Internally, Variant would need to store a hashtable keyed by method name with method information (parameter types, result type, pointer to invoker).

I think it's entirely possible to do that with what we have now, modulo some small glitches in static reflection implementation. Once we have that, we're really talking dynamic - like Python or Ruby dynamic.

>> P.S. I *love* the trick with the homonym inner namespace! It solves the naming problem so elegantly. I'll use it in RefCounted too.
>
> It was a bit surprising for me that the inner template could access member variables without being mixed in.  But I found that it's just another "template member function", and so it's legal. :-)

BTW here's what I did in RefCounted:

struct RefCounted(T)
{
     struct _RefCounted
     {
         ... stuff ...
     }
     _RefCounted RefCounted;
     ... constructor, postblit, destructor, opAssign ...
}

Seems to work fine.


Andrei
July 08, 2010
On 07/08/2010 11:01 AM, Shin Fujishiro wrote:
> Michel Fortin<michel.fortin at michelf.com>  wrote:
>> Le 2010-07-08 ? 8:13, Andrei Alexandrescu a ?crit :
>>
>>> This is solid work! There's an opportunity here - it overlaps a LOT with std.variant.Algebraic.
>>
>>
>> Indeed. It's great. But why is the protection level set to 'package'? Isn't it meant to be used outside of Phobos?
>
> I needed it to implement native codeset support in stdio.  I just went conservative for adding a public stuff that people might not use...

Cool, looking forward to seeing the result. For all I know Ruby was very successful in Japan because it had support for the Japanese character sets from day one.

>> One thing I've missed about Algebraic is the ability to switch on the type, somewhat like this:
>>
>> 	Algebraic!(A, B, C) x;
>> 	switch (x.type) {
>> 		case x.typecode!A:
>> 		case x.typecode!B:
>> 		case x.typecode!C:
>> 			...
>> 	}
>>
>> Seems like this would be easy by switching on Any's _duckID, but this facility isn't exposed (it's all private).
>
> It's nice.  I'll add it!
>
> switch (x.Algebraic.type) {
>      case x.Algebraic.typeCode!A:
>      case x.Algebraic.typeCode!B:
>     ...
>      default:
> }

I don't think that's very useful because you need to follow each case with a peek() or something. I was thinking it would be great to define a dispatcher a la Sean's receive():

x.Algebraic.dispatch(
     (A obj) {  writeln("saw an A"); },
     (B obj) {  writeln("saw a B"); },
     ...
);

Such a dispatch function could come in two flavors: strict and non-strict.


Andrei
July 10, 2010
My Any implementation used auto ref for return types.  But it turned out to be too restrictive for Algebraic.  Consider this:
----------
Algebraic!(int, BigInt) x;
auto y = ++x;
// Error: mismatched function return type inference of BigInt and int
----------

Maybe operator overloads should return Algebraic!(int, BigInt).  Then,
should we do the same for opDispatch() for unity?
----------
Algebraic!(R1, R2, ...) opDispatch(string method, Args...)(Args args)
{
    final switch (_which) {
        case A: return Algebraic( objA.method(args) );
        case B: return Algebraic( objB.method(args) );
        ...
    }
}
----------

I'm a bit reluctant to do so.  Returning Algebraic disables return-by- ref facility.  Furthermore, we can't do the following if a result is packed in an Algebraic:
----------
void fun(R)(R r) if (isSomeChar!(ElementType!R))
{
}
Algebraic!(string, CharReader) r;
r = "abc";
fun(r);     // Error!  ElementType is Algebraic!(dchar)
----------
Too bad, Algebraic r is not a 'duck' anymore.

I'm stucked.  Do you have any ideas?


Shin
July 09, 2010
On 07/09/2010 12:17 PM, Shin Fujishiro wrote:
> My Any implementation used auto ref for return types.  But it turned out to be too restrictive for Algebraic.  Consider this:
> ----------
> Algebraic!(int, BigInt) x;
> auto y = ++x;
> // Error: mismatched function return type inference of BigInt and int
> ----------
>
> Maybe operator overloads should return Algebraic!(int, BigInt).

Yes, agreed. I actually would like all overloaded operators to return void and have the language insert a reference to the object if needed.

> Then,
> should we do the same for opDispatch() for unity?
> ----------
> Algebraic!(R1, R2, ...) opDispatch(string method, Args...)(Args args)
> {
>      final switch (_which) {
>          case A: return Algebraic( objA.method(args) );
>          case B: return Algebraic( objB.method(args) );
>          ...
>      }
> }
> ----------
>
> I'm a bit reluctant to do so.  Returning Algebraic disables return-by- ref facility.

Agreed. I think it should return CommonType!(R1, R2, ...). In the particular case in which all R1, R2, ... are identical AND returned by reference, only then you can return ref R1.


Andrei