Multiple dispatch in lib was Re: Old comments about Java

Adam D. Ruppe destructionator at gmail.com
Sat Apr 23 18:30:09 PDT 2011


bearophile wrote:
> [multiple dispatch and what they use it for.]

I wonder if we could do it in the library by using overloads and
a generated dynamic cast.

class Base {
  // if the types are known, regular overloading does the job
  void multipleDispatchFun(A a, A b) { writeln("Called (A, A)"); }
  void multipleDispatchFun(A a, B b) { writeln("Called (A, B)"); }
  void multipleDispatchFun(B a, B b) { writeln("Called (B, B)"); }
}

class A : Base {}
class B : Base {}

void main() {
  Base base = new Base();
  Base a = new A();
  Base b = new B();

  // doesn't compile - static types don't match
  base.multipleDispatchFun(a, b);

  // So, we'd have to cast it ourselves and try to call
  if(dynamic casts are ok)
     base.multipleDispatchFun(cast(A) a, cast(B) b); // would work
}


It's that last part that multiple dispatch solves (someone
correct me if I'm wrong). It's a pain to do manually.

But maybe D can do it automatically. We can generate another
overload that takes the base class, tries to cast to all the
existing overloads, and if not null, go ahead and call it.

This isn't complete, but it works for this simple case so it's a
start and proof of concept:

=========

import std.traits;

template implementMultipleDispatch(alias Class, alias method) {
	//pragma(msg, multipleDispatchImpl!(Class, method,
	//	__traits(identifier, method))());

        // mixing it in right here didn't work for some reason
	alias multipleDispatchImpl!(Class, method,
		__traits(identifier, method)) implementMultipleDispatch;

}

// straight stringof hit a problem with forward reference, so this hack will
// do it

string[] parameterTypeStrings(string methodString)() {
	string[] ret;

	int startingAt;
	int state = 0;
	foreach(idx, c; methodString) {
		switch(state) {
			// first, we find the opening param
			case 0:
				if(c == '(') {
					state++;
					startingAt = idx + 1;
				}
			break;
			case 1: // we're reading a type name
				if(c == ' ') {
					// just finished
					ret ~= methodString[startingAt .. idx];
					state = 2;
				}
			break;
			case 2: // now we're reading a name
				if(c == ' ') {
					state = 1; // another type
					startingAt = idx + 1;
				}
				if(c == ')') // we're done
					//break loop;
					state = 3;
			break;
			// we're done, but break loop doesn't work in CTFT
			case 3: // do nothing with the rest
		}
	}

	return ret;
}

string multipleDispatchImpl(alias Class, alias method, string methodName)() {
	string code = `void ` ~ methodName ~ `(`;

	alias typeof(__traits(getOverloads, Class, methodName)) overloads;

	string baseType = Class.stringof;
	bool outputted = false;

	// we'll reuse the call string to make our calls
	string call = methodName ~ "(";
	char arg_char = 'a';
	foreach(arg; ParameterTypeTuple!(method)) {
		if(outputted) {
			code ~= ", ";
			call ~= ", ";
		} else
			outputted = true;

		code ~= baseType ~ " " ~ arg_char;
		call ~= " " ~ arg_char ~ "_casted";
		arg_char++;
	}

	call ~= ");";
	code ~= ") {";

	foreach(overload; overloads) {

		code ~= "{"; // we'll introduce a scope to do our casts
		string ifCheck; // this lists the checks for null
		outputted = false;
		arg_char = 'a';

		foreach(argument; parameterTypeStrings!(overload.stringof)) {
			// just try to cast all of them
			code ~= "auto ";
			code ~= arg_char ~ "_casted = cast(";

			code ~= argument;

			code ~= ") " ~ arg_char ~ ";";

			if(outputted)
				ifCheck ~= " && ";
			else
				outputted = true;

			ifCheck ~= arg_char ~ "_casted !is null";

			arg_char++;
		}

		// if none of the args are null, it's safe to do the call
		code ~= "if(" ~ ifCheck ~ ") {";
		code ~= call ~ "return;"; // return because we're done
		code ~= "}"; // end if
		code ~= "}"; // close our casting scope
	}

	code ~= "}";

	return code;
}

==============



Here's a test using it:

import std.stdio;

// copy paste the above in here


class A : Base {}
class B : Base {}

class Base {
	void multipleDispatchFun(A a, A b) { writeln("Called (A, A)"); }
	void multipleDispatchFun(A a, B b) { writeln("CORRECT Called (A, B)"); }
	void multipleDispatchFun(B a, B b) { writeln("Called (B, B)"); }

	mixin (implementMultipleDispatch!(typeof(this), multipleDispatchFun));
}


void main() {
	Base base = new Base();
	Base a = new A();
	Base b = new B();

        // calls the generated base class overload
	base.multipleDispatchFun(a, b);

        // and, of course, if the static types are known, the
        // regular overload works fine. only problem is if one
        // is statically known and one is not. Then you shoud
        // cast to the base type manually so it triggers the
        // generated overload
}

========


This is a toy example, but if someone really wanted to spend the
time, I think it proves it can be done for real examples too, at
least in theory.


More information about the Digitalmars-d mailing list