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