Fully dynamic d by opDotExp overloading

Adam Burton adz21c at googlemail.com
Sun Apr 19 07:00:47 PDT 2009


BCS wrote:

> Hello Adam,
> 
>> On Sat, Apr 18, 2009 at 06:10:27PM -0700, Andrei Alexandrescu wrote:
>> 
>>> The point of using "." is not syntactic convenience as much as the
>>> ability of the Dynamic structure to work out of the box with
>>> algorithms that use the standard notation.
>>> 
>> What if the dot remained exactly like it is now and the -> took
>> the place of the dot in the proposal; regular method calls when
>> possible and forwarded to opExtension (opDotExp needs a better name)
>> when that fails?
>> Thus your generic algorithm can use -> and work in all cases, and the
>> dot operator remains the same as it is now.
> 
> Going the other way would be better; '.' works as Andrei wants and '->' is
> an explicit, "don't use the dynamic stuff" invocation. If it went the
> other way virtually all template code would end up needing to use '->' for
> everything just in cases someone wants to pass in a type that uses
> opDotExp.
Yea and that would be bad, since then as far as I am concerned you have 
destroyed the purpose of templates. Seems to me templates and dynamic calls 
are sorta there to solve the same problem, how do you write code that is 
independent of type? Dynamic takes it to extreme and makes the evaluation of 
methods (or public types variables I don't see how that would be different) 
and leave it all to runtime which is far more flexible but potentially has 
holes if the type doesn't meet the requirements. Templates take a more 
conservative route by still letting you write independent of type but then 
using the information provided at call time it can still perform static code 
checks to make sure the type meets its needs, making it a little more safe 
than dynamic. However soon as you throw some dynamic calls into a template 
the guarantees usually provided go out the window since the template is 
letting that none existent member slip by. Consider below, why not just use 
a completely dynamic function as at least then you know what you are letting 
yourself in for?:

void function(T)(T i)
{
   i.bob();
}

void function(Object i)
{
   i.bob();
}

In the above both functions do exactly the same thing, only difference is 
with the template sometimes the call is static sometimes and other times it 
isn't. All you are saving yourself is maybe a performance improvement (which 
I admit I would probably use the template for just that reason)? However 
you've lost the compile-time checking of templates and still add the 
possibility of typos or bad method names leaking in (consider in my previous 
example if I removed the open method from ServerProxy, with opDotExp it 
would take over control of the call to 'open' if I happen to forget to 
remove the call from somewhere).

However then I suppose then you are still screwing over the half and half 
people by not using dynamic calls in templates.

T function(T)(T i)
{
   i.bob();
   return i;
}

Variant function(Variant i)
{
   i.bob();
   return i;
}

Using the dynamic method the user is forced to perform a cast where as you 
don't with the template version.

Ok, proposal draft 2 :-P (bare with me this is mostly a mind dump so I 
apologize for if I may seem to ramble).

So what I said previously still stands. StaticAsMuchAsPoss lives with best 
of both worlds and therefore if I tomorrow decided to remove the method 
"open" it would fail with a compile-time error where as  DynamicAsMuchAsPoss 
leaves it till run-time. Object/Variant, or whatever opArrow returns, would 
implement default behaviour (and opArrow would be abstract) of searching the 
type for implementations of the provided function name (or it could be a 
public variable name) and if such a thing exists it executes it else throws 
a runtime error. This allows what you see in DynamicAsMuchAsPoss and  
AlsoShowBypassNeedToUpcast to be possible, as well as code like something-
>bob(10)->nono(11) without worrying about casts (if nono was implemented as 
an actual function, so we live dynamically). So we get sort of below.

class Object
{
   .... usual ....
   Object opArrow(char[] name, v...)
   {
       if (this.publicmethodexists(name))
          return invoke(name, this, v);
       else if (this.publicvariableexists(name))
          return invoke(name, this);
       else 
          throw NoSuchMemberException(name, v);
   }
   .... more usual ....
}

class A
{
    void bob() {}
    void opArrow(char[] name, v ...)
    {
        if (name == "cheese")
           dosomething();
        else
           super.opArrow(name, v);
    }
}

void main()
{
    A a = new A();
    a.bob();	// Compile time valid
    a.cheese();	// compile time invalid
    a->cheese();	// runtime valid
    a->bob();          // runtime valid
    a.nono();	// Compile time invalid
    a->nono();	// Runtime invalid

    Object obj = a;
    obj.bob();	// Compile time invalid
    obj.cheese();	// compile time invalid
    obj->cheese();	// runtime valid
    obj->bob();        // runtime valid
    obj.nono();	// Compile time invalid
    obj->nono();	// Runtime invalid
}

As for templates then how about a dynamic template call that does 1 or 2 
passes through the code to prepare it to be dynamic? How about we replace 
the '!' with a '?' for the dynamic call, that way we can have strict 
templates (!) and dynamic temapltes (?). See below using the previously 
discussed template.

T myfunc(T i)
{
    i.bob();
    return i;
}

class MyStrictClass
{
    void bob() {}
}

MyStrictClass c = myfunc!(MyStrictClass)(new MyStrictClass());	// strict 
call converts to below (default behaviour where i am not verbose with !() )

MyStrictClass myfunc(MyStrictClass i)
{
    i.bob();
    return i;
}


class MyDynamicClass
{
    void opArrow(char[] name, v ...)
    {
        if (name == "bob")
          something;
        else
           super.opArrow(name, v);
    }
}

MyDynamicClass c = myfunc?(MyDynamicClass)(new MyDynamicClass());	// dynamic 
call converts to below

// It goes through and each . call is converted to a dynamic call
MyDynamicClass myfunc(MyDynamicClass i)
{
    i->bob();
    return i;
}

The above becomes completely dynamic. However that could make it all slow 
due to dynamic calls everywhere which in some cases may be unnecessary, 
think about below

T myfunc(T i)
{
    i.bob();
    i.nono();
    return i;
}

class MyStrictClass
{
    void bob() {}
    void nono() {}
}

class MyDynamicClass
{
    void bob() {}
    void opArrow(char[] name, v ...)
    {
        if (name == "nono")
          something;
        else
           super.opArrow(name, v);
    }
}

// static myfunc!(MyStrictClass)
MyStrictClass myfunc(MyStrictClass i)
{
    i.bob();
    i.nono();
    return i;
}

// dynamic myfunc?(MyDynamicClass)
MyDynamicClass myfunc(MyDynamicClass i)
{
    i->bob();	// This is unnecessary
    i->nono();
    return i;
}

// dynamic alternative myfunc?(MyDynamicClass)
MyDynamicClass myfunc(MyDynamicClass i)
{
    i.bob();	// Was identified as being there so kept as static
    i->nono();	// Wasn't found in type definition so made dynamic
    return i;
}

So now we get the template attempting to use the best of both worlds and 
only giving in to being dynamic where necessary, it means that in some cases 
your dynamic templates would not need to be dynamic at all.

The next place I see some potential issue is return types of methods call 
from dynamic inside the template where it may be again unnecessarily using 
dynamic.

T myfunc(T,U)(T t)
{
    U u = t.bob();
    u.something();
    return t;
}

class A
{
   Object opArrow(char[] name, v ...)
   {
       if (name == "bob")
         return call("bob", v);
       else
         return super.opArrow(name, v);
   }
}

class B
{
   void something() {}
}

A a = myfunc?(A,B)(new A()); // becomes
A myfunc(A t)
{
    B u = t->bob();	// Compile time error, not variant/object
    u.something();
    return t;
}

You could get around above by replacing U with auto

T myfunc(T)(T t)
{
    auto u = t.bob();
    u.something();
    return t;
}
A a = myfunc?(A)(new A()); // becomes
A myfunc(A t)
{
    auto u = t->bob();	// Gets return as object so from here on it starts to 
unfold that the rest of the template is dynamic
    u->something();   // Since this is not object/variant it is forced to 
dynamic
    return t;
}

However that doesn't fix below.

U myfunc(T,U)(T t)
{
    auto u = t.bob();
    u.something();
    return u;
}
B b = myfunc?(A,B)(new A()); // becomes
B myfunc(A t)
{
    auto u = t->bob();
    u->something();   // Since this is not object/variant it is forced to 
dynamic
    return u;	// Doesn't work
}

Therefore we could add a rule that anything returned in a template from a 
dynamic call that is being passed to a template variable type is cast to 
that type (I don't like to just throw casts about, but seems to me that its 
in the same league as dynamic calls). So the below can happen instead.

U myfunc(T,U)(T t)
{
    U u = t.bob();
    u.something();
    return u;
}
B b = myfunc?(A,B)(new A()); // becomes
B myfunc(A t)
{
    B u = cast(B) t->bob();	// Here is the dynamic aspect of the template
    u.something();	// We are using B not Object so we can go static in our 
call
    return u;
}

You could argue the above starts making templates more complicated, however 
I think these are already the sort of things you have to bare in mind with 
templates and dynamic types anyway.

Do that solve the problem?

p.s. I can see an issue with other operators turning dynamic in templates if 
you convert 'a = a + a;' to 'a = a.opAdd(a);' and don't implement opAdd in 
the type passed into dynamic template call, but that depends how you process 
the template call ..  I don't know :-). I would avoid letting such a thing 
happen (process the + to opAdd after its done a dynamic pass) otherwise you 
add a rule that operators can be dynamic in templates but not normal code 
which is confusing.




More information about the Digitalmars-d mailing list