Fully dynamic d by opDotExp overloading

Michel Fortin michel.fortin at michelf.com
Sun Apr 19 07:13:48 PDT 2009


On 2009-04-18 22:21:50 -0400, Andrei Alexandrescu 
<SeeWebsiteForEmail at erdani.org> said:

> I did, but sorry, it doesn't make sense and does nothing but continue 
> the terrible confusion going in this thread.

Then let's try to remove some of that confusion.


> You say: well if opDot were a template then a scripting language can't 
> get to it because what it has is a dynamic string. Yes. That's why 
> opDot does whatever static string processing and then forwards, if the 
> design asks for it, to the dynamic function. opDot's participation is 
> solely a syntax helper within D.

Yes, I understand that template opDot as you proposed is just a syntax helper.

I'm arguing that many uses proposed for this syntax helper are better 
implemented by mixins (because functions added by mixin are accessible 
using reflection, while functions added by a templated syntax helper 
are not). Of the remaining uses that can't be served by mixins, most 
need runtime dispatch, which can be made compatible with a 
runtime-dispatch system if there is a standard name for a non-template 
catch-all function (as opposed to a user-defined name you can only know 
from within a template opDot). And the remaining ones that require a 
template opDot are not really useful (case-insentiveness?).


That was the summary. Following is the detailed explanation. Now, 
Andrei, if you think something doesn't make any sense in all this, 
please tell me where it stops making sense so I can understand were 
we're disagreeing.


Let's consider how runtime reflection works. Basically, you have a list 
of available functions for a given type, and their function pointers. 
When you want to invoke a function using runtime reflection, you find 
it in the list, and call the pointer. Something like this (simplified) 
function:

	void invokeViaReflection(string name)
	{
		void function()* fptr = name in functionList;
		if (fptr)
			ftpr();
		else
			throw Exception("Function "~ name ~" not found.");
	}

(Note that you can already this kind of reflection by building the list 
at compile time for the types you want with something like ClassInfoEx, 
but this could also be built into the compiler.)

So if you have an external script that wants to call a D function, or 
you receive a remote invocation via network, the script will call that 
invokeViaReflection function above which will call the right function.

Now, let's say we want to add the ability for a D class to act as a 
proxy for another object, then we need to catch calls to undefined 
functions and do something with them (most likely forward them to 
someone else). For that to be reflected to the script, we'll have to 
modify the invokeViaReflection like this:

	void invokeViaReflection(string name)
	{
		void function()* fptr = name in functionList;
		if (fptr)
			ftpr();
		else
		{
			void function(string name)* catchAll = "catchAllHandlerFunc" in 
functionList;
			if (catchAll)
				catchAll(name);
			else
				throw Exception("Function "~ name ~" not found.");
		}
	}

The thing is that the name of that "catchAllHandlerFunc" function needs 
to be standardised for it to work with runtime reflection. If the 
standardized way of doing this is to implement things in a template and 
from that template call a catch-all function with a user-defined, 
unpredictable name, then we can't use the catch-all function at all 
with runtime reflection since the name of that runtime catch-all 
function is burried in a template.

This was my first point. I hope it makes sense and that we agree about 
the conclusion: there needs to be a standardized name for a 
non-template catch-all function if we want runtime reflection to 
reflect the compile-time dispatching process.

(By the way this, in itself doesn't preclude having a template 
catch-all too, only if there is one the runtime one should be 
standardized too.)


Now, for my second point, let's consider some use cases for a catch-all 
mecanism.

Let's look at the swizzle case. You want a class or a struct with a 
function for every permutation of "xwyz". Beside doing that manually, 
here's what you can do:

Solution A: use a mixin. This will add 256 functions to your struct, 
and those functions will appear in the functionList used by 
invokeViaReflection.

Solution B.1: use a templated catch-all function, and use the name to 
instanciate the catch-all. Calling the function will work at compile 
time, but at runtime invokeViaReflection is left in the dust.

Solution B.2: in addition to the templated catch-all function in B.1, 
add a runtime catch-all. This is about twice the work, but 
invokeViaReflection can work with those functions.

So of all the solutions above, B.2 is twice the work and adds the risk 
of the two implementations becoming out of sync, and is probably slower 
than A when called through invokeViaReflection. B.1 breaks 
invokeViaReflection. And A works for everything. My conclusion is that 
solution A is preferable. And I think the language should encourage the 
use of solution A in this case.


Now, let's look at the proxy case, where you want to forward function 
calls to another object. In fact, there are two variant of this case: 
one where you know the type at compile-time, one where you don't (you 
only know the base class) but still want to forward all calls.

Solution A: use a mixin. The mixin will create a wrapper function for 
all functions in the wrapped object it can find using compile-time 
reflection, and those functions will appear in the functionList used by 
invokeViaReflection.

Solution B.1: use a templated catch-all function which will simply 
issue a call on the wrapped object. Calling the function will work at 
compile time, but at runtime invokeViaReflection is left in the dust if 
we try to use it on our proxy.

Solution B.2: in addition to the templated catch-all function in B.2, 
add a runtime catch-all that will check the runtime string against the 
function names obtained at compile time and call the right function. 
This is more than twice the work, but invokeViaReflection can call 
functions on our proxy.

Solution C: create a runtime catch-all function that will use 
invokeViaReflection to call the right function on the wrapped object.

Note that solution C is the only option if you don't know the exact 
type at compile-time, like when you only know the base class. 
Unfortunately, solution C for our proxy won't work if the wrapped 
object uses B.1. Same for solution A, which relies on compile-time 
reflection.

So that's why I think B.1 (templated catch-all function) should be 
avoided whenever a mixin solution is possible. In the two use cases 
above, you can use a mixin and get the same "staticness" as in B, but 
the mixin solution also offer support for reflection, which makes it 
superior for the cases you're stuck using the object at runtime, or 
when we want to introspect at compile-time. And when a mixin doesn't 
cut it, the template will most likely just be a stub for calling a 
runtime mecanism.


Note that I'm not that much against a templated opDot, I just want to 
point out its drawbacks for reflection. I'm also unconvinced of its 
usefulness considering you can do the same things better using mixins 
leaving only the runtime case to be solved, where you are better served 
without a template anyway.

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




More information about the Digitalmars-d mailing list