Fully dynamic d by opDotExp overloading
Adam Burton
adz21c at googlemail.com
Sun Apr 19 07:17:22 PDT 2009
Adam Burton wrote:
> 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.
Just thought as well, in a dynamic template call you would probably disable
certain kinds of template constraints. Consider below wouldn't work for a
dynamic call without disabling the interface constraint.
interface A
{
void bob();
}
class B
{
void opArrow(char[] name, v...)
{
if (name == "bob")
something;
else
super.opArrow(name, v);
}
}
void function(T : A)(T i)
{
i.bob();
}
function?(B)(new B()); // Won't work, B does not meet criteria
On another note:
function?(new B()); // Potential short hand using type infurance(sp?)??
although if you replace '?' with '!' then the short hand becomes ambiguous
but I suppose since I say strict should be default then short hand for
strict isn't necessary
More information about the Digitalmars-d
mailing list