Jump to page: 1 2
Thread overview
Old comments about Java
Apr 23, 2011
bearophile
Multiple dispatch in lib was Re: Old comments about Java
Apr 24, 2011
Adam D. Ruppe
Apr 24, 2011
KennyTM~
Apr 24, 2011
Adam D. Ruppe
Apr 25, 2011
Andrej Mitrovic
Apr 24, 2011
Paulo Pinto
Apr 24, 2011
Adam D. Ruppe
Apr 29, 2011
Bruno Medeiros
Apr 30, 2011
Walter Bright
Apr 30, 2011
Peter Alexander
May 01, 2011
Sean Kelly
April 23, 2011
I've re-read an old (1997-2000) article about Java. The author seems an expert lisper. Of course some of those comments about Java are obsolete today:
http://www.jwz.org/doc/java.html

I don't remember if I have already shown this article here, I have found only this, from 2001: http://www.digitalmars.com/pnews/read.php?server=news.digitalmars.com&group=D&artnum=1319


Some quotations from the article. Some of the following things apply to D too (probably as seen from a CLisp programmer). My few comments are in []:

The fact that static methods aren't really class methods (they're actually global functions: you can't override them in a subclass) is pretty dumb.

This "integers aren't objects" nonsense really pisses me off. Why did they do that? Is the answer as lame as, "we wanted the 'int' type to be 32 bits instead of 31"? (You only really need one bit of type on the pointer if you don't need small conses, after all.)
[In D you are able to define such single-word integers in library code, but they are not the default integer type.]

After all this time, people still think that integer overflow is better than degrading to bignums, or raising an exception?
[Of course not. Maybe D devs will get there.]

I sure miss multi-dispatch. (The CLOS notion of doing method lookup based on the types of all of the arguments, rather than just on the type of the implicit zero'th argument, this).
[Andrei may Ask some CLisp programmers why they like multi-dispatch and what they use it for.]

The finalization system is lame. Worse than merely being lame, they brag about how lame it is! To paraphrase the docs: "Your object will only be finalized once, even if it's resurrected in finalization! Isn't that grand?!" Post-mortem finalization was figured out years ago and works well. Too bad Sun doesn't know that.
[I think this is getting fixed in D2]

Relatedly, there are no "weak pointers." Without weak pointers and a working finalization system, you can't implement a decent caching mechanism for, e.g., a communication framework that maintains proxies to objects on other machines, and likewise keeps track of other machines' references to your objects.
[Weak pointers will eventually be present in Phobos]

The locking model is broken.

    First, they impose a full word of overhead on each and every object, just in case someone somewhere sometime wants to grab a lock on that object. What, you say that you know that nobody outside of your code will ever get a pointer to this object, and that you do your locking elsewhere, and you have a zillion of these objects so you'd like them to take up as little memory as possible? Sorry. You're screwed.
    [I have not yet understood why D shared this Java design choice.]

    Any piece of code can assert a lock on an object and then never un-lock it, causing deadlocks. This is a gaping security hole for denial-of-service attacks.

    In any half-way-rational design, the lock associated with an object would be treated just like any other slot, and only methods statically "belonging" to that class could frob it.


There is no way to signal without throwing: that is, there is no way to signal an exceptional condition, and have some condition handler tell you "go ahead and proceed anyway." By the time the condition handler is run, the excepting scope has already been exited.

The distinction between slots and methods is stupid. Doing foo.x should be defined to be equivalent to foo.x(), with lexical magic for "foo.x = ..." assignment. Compilers should be trivially able to inline zero-argument accessor methods to be inline object+offset loads. That way programmers wouldn't break every single one of their callers when they happen to change the internal implementation of something from something which happened to be a "slot" to something with slightly more complicated behavior.
[This is fixed/done in Scala language]

The notion of methods "belonging" to classes is lame. Anybody anytime should be allowed to defined new, non-conflicting methods on any class (without overriding existing methods.) This causes no abstraction-breakage, since code which cares couldn't, by definition, be calling the new, "externally-defined" methods. This is just another way of saying that the pseudo-Smalltalk object model loses and that generic functions (suitably constrained by the no-external-overrides rule) win.
[This last idea looks like C# extension methods]

Bye,
bearophile
April 24, 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.
April 24, 2011
bearophile wrote:
> Doing foo.x should be defined to be equivalent to foo.x(), with
> lexical magic for "foo.x = ..." assignment.
> [This is fixed/done in Scala language]

And in D: that's exactly how our properties are implemented right now.

> [This last idea looks like C# extension methods]

And like UFCS, like we have on arrays.
April 24, 2011
On Apr 24, 11 09:30, Adam D. Ruppe wrote:
> 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)"); }
> }
>

This thing is a triple-dispatch. I think these should be static functions?

BTW, you toy example is way too long. 40 lines of template mixin is  enough:

import std.traits, std.typetuple;
template GeneralizeType(T) {
    static if (is(T == class))
        alias Object GeneralizeType;
        //@@@BUG@@@ Should preserve qualifier of T.
    else
        alias T GeneralizeType;
}
mixin template ImplementMultipleDispatch(alias method, alias Parent = __traits(parent, method)) {
    private enum methodName = __traits(identifier, method);
    private alias staticMap!(GeneralizeType, ParameterTypeTuple!method) ParamType;
    //@@@BUG3543@@@ Should take the most common type from all overloads of the
    //              method, but currently it is very complicated due to 3543, so
    //              we just default to use Object.
    alias method opCall;
    static auto opCall(ParamType params) {
        writeln(" ***called the multiple dispatch method");
        foreach (overloadedMethod; __traits(getOverloads, Parent, methodName)) {
            //@@@BUG@@@ Should sort overloads by specialization.
            //@@@BUG@@@ Should inherit function attributes from the method
            ParameterTypeTuple!overloadedMethod specializedParam;
            bool matched = true;
            foreach (i, T; ParameterTypeTuple!overloadedMethod) {
                specializedParam[i] = cast(T)params[i];
                static if (is(T == class))
                    if (specializedParam[i] is null) {
                        //@@@BUG@@@ What if it is not a parameter for dispatch
                        //          but is null? Need to check that.
                        matched = false;
                        break;  // continue label doesn't work.
                    }
            }
            if (matched)
                return overloadedMethod(specializedParam);
        }
        throw new Exception("Cannot find suitable overload in " ~ methodName);
    }
}

//------------------------------------------------------------------------------

import std.stdio;

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

class Base {
    static private {
        int someFuncImpl(A x, A y, B z, B w, double t) {
            writeln("A, A, B, B, ", t);
            return 3;
        }
        int someFuncImpl(A x, B y, A z, B w, double t) {
            writeln("A, B, A, B, ", t);
            return 2;
        }
        int someFuncImpl(A x, A y, A z, A w, double t) {
            writeln("A, A, A, A, ", t);
            return 1;
        }
    }

    mixin ImplementMultipleDispatch!someFuncImpl someFunc;
}

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

    assert(1 == Base.someFunc(aa, aa, aa, aa, 0.4));
    assert(3 == Base.someFunc(aa, aa, bb, bb, 0.3));

    assert(1 == Base.someFunc(a, a, a, a, 0.5));
    assert(3 == Base.someFunc(a, a, b, b, 0.6));
    assert(2 == Base.someFunc(a, b, a, b, 0.7));
    Base.someFunc(b, b, b, b, 0.8);  // throws
}
April 24, 2011
Multiple dispatch is commonly used for fixing the "expression problem". http://en.wikipedia.org/wiki/Expression_problem

This is usually easily done in functional languages, and relates to the best
way to adapt
existing code to new uses, without changing the underlining code.

Multimethods provide a very good solution to the problem.

http://channel9.msdn.com/Shows/Going+Deep/C9-Lectures-Dr-Ralf-Laemmel-Advanced-Functional-Programming-The-Expression-Problem
http://www.ibm.com/developerworks/java/library/j-clojure-protocols/?ca=drs-

--
Paulo

"Adam D. Ruppe" <destructionator@gmail.com> wrote in message news:iovuf1$2dhi$1@digitalmars.com...
> 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.


April 24, 2011
KennyTM~ wrote:
> I think these should be static functions?

At first my idea was free functions, but getOverloads only works with classes, so I pasted them in without really thinking about it.

But yes, I think you're right.

> BTW, you toy example is way too long. 40 lines of template mixin is  enough:

Heh, that's awesome. Thanks!
April 25, 2011
On 4/24/11 10:53 AM, Adam D. Ruppe wrote:
> KennyTM~ wrote:
>> I think these should be static functions?
>
> At first my idea was free functions, but getOverloads only works with
> classes, so I pasted them in without really thinking about it.
>
> But yes, I think you're right.
>
>> BTW, you toy example is way too long. 40 lines of template mixin
>> is  enough:
>
> Heh, that's awesome. Thanks!

I think a well-thought and well-documented multiple dispatch library would be a valuable addition to Phobos. Don't forget that a major issue is modularity.

Andrei
April 25, 2011
Here's another implementation: http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=56018
April 29, 2011
On 24/04/2011 00:43, bearophile wrote:
>      First, they impose a full word of overhead on each and every object, just in case someone somewhere sometime wants to grab a lock on that object. What, you say that you know that nobody outside of your code will ever get a pointer to this object, and that you do your locking elsewhere, and you have a zillion of these objects so you'd like them to take up as little memory as possible? Sorry. You're screwed.
>      [I have not yet understood why D shared this Java design choice.]

Huh? D hasn't shared that design choice at all: unlike Java, D has structs which can be used for lightweight data structures... Duh.

-- 
Bruno Medeiros - Software Engineer
April 30, 2011
On 4/23/2011 4:43 PM, bearophile wrote:
> First, they impose a full word of overhead on each and every object, just in
> case someone somewhere sometime wants to grab a lock on that object. What,
> you say that you know that nobody outside of your code will ever get a
> pointer to this object, and that you do your locking elsewhere, and you have
> a zillion of these objects so you'd like them to take up as little memory as
> possible? Sorry. You're screwed. [I have not yet understood why D shared this
> Java design choice.]

The extra pointer slot is a handy place for all kinds of things, not just a mutex. Currently, it is also used for the "signals and slots" implementation. Andrei and I have discussed using it for a ref counting system (though we decided against that for other reasons).
« First   ‹ Prev
1 2