www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Old comments about Java

reply bearophile <bearophileHUGS lycos.com> writes:
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
next sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
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
next sibling parent reply KennyTM~ <kennytm gmail.com> writes:
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 }
Apr 24 2011
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
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
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
Apr 25 2011
parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
Here's another implementation:
http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=56018
Apr 25 2011
prev sibling parent "Paulo Pinto" <pjmlp progtools.org> writes:
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
prev sibling next sibling parent Adam D. Ruppe <destructionator gmail.com> writes:
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
prev sibling next sibling parent Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
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
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
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
parent reply Peter Alexander <peter.alexander.au gmail.com> writes:
On 30/04/11 8:29 PM, Walter Bright wrote:
 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).
That may be so, but it would be nice if the programmer had control over whether or not they want to use that slot.
Apr 30 2011
parent Sean Kelly <sean invisibleduck.org> writes:
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:
 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.]
=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).
=20 That may be so, but it would be nice if the programmer had control over wh=
ether or not they want to use that slot.
Apr 30 2011