digitalmars.D - Old comments about Java
- bearophile (28/28) Apr 23 2011 I've re-read an old (1997-2000) article about Java. The author seems an ...
- Adam D. Ruppe (149/150) Apr 23 2011 I wonder if we could do it in the library by using overloads and
- KennyTM~ (82/92) Apr 24 2011 This thing is a triple-dispatch. I think these should be static function...
- Adam D. Ruppe (5/8) Apr 24 2011 At first my idea was free functions, but getOverloads only works with
- Andrei Alexandrescu (5/13) Apr 25 2011 I think a well-thought and well-documented multiple dispatch library
- Andrej Mitrovic (2/2) Apr 25 2011 Here's another implementation:
- Paulo Pinto (12/164) Apr 24 2011 Multiple dispatch is commonly used for fixing the "expression problem".
- Adam D. Ruppe (3/7) Apr 23 2011 And like UFCS, like we have on arrays.
- Bruno Medeiros (5/7) Apr 29 2011 Huh? D hasn't shared that design choice at all: unlike Java, D has
- Walter Bright (5/12) Apr 30 2011 The extra pointer slot is a handy place for all kinds of things, not jus...
- Peter Alexander (3/20) Apr 30 2011 That may be so, but it would be nice if the programmer had control over
- Sean Kelly (5/26) Apr 30 2011 They kinda already do. Look into how core.mutex works.=20
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. Bye, bearophile
Apr 23 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.
Apr 23 2011
On Apr 24, 11 09:30, Adam D. Ruppe wrote:bearophile wrote: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 }[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)"); } }
Apr 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!
Apr 24 2011
On 4/24/11 10:53 AM, Adam D. Ruppe wrote:KennyTM~ wrote: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. AndreiI 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!
Apr 25 2011
Here's another implementation: http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=56018
Apr 25 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.
Apr 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.And like UFCS, like we have on arrays.
Apr 23 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
Apr 29 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).
Apr 30 2011
On 30/04/11 8:29 PM, Walter Bright wrote:On 4/23/2011 4:43 PM, bearophile wrote:That may be so, but it would be nice if the programmer had control over whether or not they want to use that slot.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).
Apr 30 2011
They kinda already do. Look into how core.mutex works.=20 Sent from my iPhone On Apr 30, 2011, at 1:43 PM, Peter Alexander <peter.alexander.au gmail.com> w= rote:On 30/04/11 8:29 PM, Walter Bright wrote:ether or not they want to use that slot.On 4/23/2011 4:43 PM, bearophile wrote:=20 That may be so, but it would be nice if the programmer had control over wh=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.]=20 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).
Apr 30 2011