Alternatives languages on the JVM: which one or is there no alternative?

J

Joshua Cranmer ðŸ§

I think, the diamond problem w.r.t fields is over-rated.

Given your response below, I think you misunderstand the problem. Let me
try to explain it better. There are two separate issues: name
resolution, and class layout. Name resolution is relatively simple to
solve; it's the layout that is the thorny solution.

When you layout a class in memory, it is easiest to think of it as an
array of "slots" for actual field storage (think C structs if you're
used to that). Class layout is typically done by laying out the
superclass first and then using extra slots for the subclass. If done
this way, this lets you guarantee that all objects of class A, slot 0
always points to the same field, even if the object's dynamic type is
really a subclass of A.

Multiple inheritance throws a wrinkle into this scheme because of the
diamond problem. If D inherits from B and C, which both inherit from A,
then the standard way of laying out the class yields two copies of A. If
you cast D to type C, then a method of C that tries to access a field of
type A would access C's copy of A instead of B (and presumably D's
"primary" copy)'s copy. This is the first case I give. In this
situation, accessing a field only requires knowing the static type of
the object, not the dynamic type.

You can get around this by requiring knowledge about dynamic types. The
option taken by C++'s virtual bases is to have the class layout
mechanism create only one copy of A. In this case, there is no longer a
guarantee that the field is always at the same offset for the static
type of an object--you have to do a layer of indirection to find out
where A's slots are and then use those. This is case #3 (note that the
casting to use a superclass's fields is implicit here).

The final option is to refer to fields internally via a hashtable of
(qualified) name to their values. This is generally what is done in most
dynamic programming languages (Python, JS, etc.), which is why they can
suffer multiple inheritance: all property accesses already go through
the hashtable, so there is no extra cost. This is what I called case #2.
So, if some entity "is-a"nother entity, but "is-also-a" yet another
entity, then why is Java praised for forcing me to model an "is-also-a"
relation as a "has-a"?

There are certainly cases where multiple inheritance is warranted; I am
not disputing that. That said, there are also people who try to inherit
for the wrong reasons, so probably most cases where people think they
want to use multiple inheritance, they don't. (I'm speaking in
generalities, though, and all generalities are false).
 
S

Saxo

Am Freitag, 29. November 2013 00:03:53 UTC+1 schrieb Silvio:
This will sooner make the result less readable, less
thread-safe and generally more error prone than if Java8 would have had
full closures.

What do you mean by "full closures"? That closure variables are not only readable, but also writable? Non-local returns missing? Just curious as to what full closures are expected to be.

Thanks, Saxo
 
S

Silvio

Am Freitag, 29. November 2013 00:03:53 UTC+1 schrieb Silvio:

What do you mean by "full closures"? That closure variables are not only readable, but also writable? Non-local returns missing? Just curious as to what full closures are expected to be.

Thanks, Saxo

You are right, that is a vague term. And yes, that is what I mean. In
addition there are some syntactical limitations that make Java8 lambdas
stand out from their surrounding code.

That is something that could be improved upon with some extra language
features but I am afraid these would not go down easy with the
conservative Java crowd, which is a pity. Anyone interested should look
up Scala thunks and its alternative call syntax (infix call notation,
multiple parameter lists, using braces, implicit call of apply/update
etc). Nothing magical, simply some pragmatic additions that allow for
much cleaner and more readable user code.

The current lambdas are an obvious compromise between people who would
have welcomed more far fetched closure semantics and syntax versus
people who where opposed to adding closures in the first place. That is
understandable but I doubt having partial closures is better than not
having them at all. I think it is unlikely that the current lambdas will
evolve into full closures. Perhaps waiting a couple more years until the
Java world became more receptive to the idea would have made it possible
to do it right at once?
 
S

Saxo

Am Freitag, 29. November 2013 12:05:37 UTC+1 schrieb Silvio:
The current lambdas are an obvious compromise between people who would

have welcomed more far fetched closure semantics and syntax versus

people who where opposed to adding closures in the first place. That is

understandable but I doubt having partial closures is better than not

having them at all. I think it is unlikely that the current lambdas will

evolve into full closures. Perhaps waiting a couple more years until the

Java world became more receptive to the idea would have made it possible

to do it right at once?

AFAIK Java8 lambdas are somewhat less verbose wrappers around anonymous inner classes. When I saw that closure vars inside Java8 lambdas are final my first thought was that obviously Java is from now on on maintenace and that's it. The language Java won't evolve any more. I guess things will just stay like that from now on as the case for Cobol or something. The focus willbe on tools, framewors, systems, etc. as it already has been for a while Ifear ...
 
S

Silvio

Am Freitag, 29. November 2013 12:05:37 UTC+1 schrieb Silvio:


AFAIK Java8 lambdas are somewhat less verbose wrappers around anonymous inner classes. When I saw that closure vars inside Java8 lambdas are final my first thought was that obviously Java is from now on on maintenace and that's it. The language Java won't evolve any more. I guess things will just stay like that from now on as the case for Cobol or something. The focus will be on tools, framewors, systems, etc. as it already has been for a while I fear ...

That is very likely, unfortunately. Personally I can live with that
since I only use Java for legacy purposes and have done so for about
five years now. Over 95% of the Java code I dive into now and then is
even pre-1.5 (you know, casts, raw types, etc.).

But Java does not only serve a role as the status quo language for the
JVM. It is also the most prominent arena where the new kids arrive.
Unless one or some of the alternative JVM languages take over that role
the JVM will (and already has) become less and less compelling to the
rock-stars to be. The university I once attended now uses C# as the
language for its primary entry level programming course. That used to be
Java (not in my time of course, I learned programming with Algol68) ...
 
S

Silvio

That is very likely, unfortunately. Personally I can live with that
since I only use Java for legacy purposes and have done so for about
five years now. Over 95% of the Java code I dive into now and then is
even pre-1.5 (you know, casts, raw types, etc.).

But Java does not only serve a role as the status quo language for the
JVM. It is also the most prominent arena where the new kids arrive.
Unless one or some of the alternative JVM languages take over that role
the JVM will (and already has) become less and less compelling to the
rock-stars to be. The university I once attended now uses C# as the
language for its primary entry level programming course. That used to be
Java (not in my time of course, I learned programming with Algol68) ...

Of course I didn't, I learnt programming with Algol68...
 
A

Andreas Leitgeb

Joshua Cranmer 🧠said:
Given your response below, I think you misunderstand the problem.

My response was already trimmed a bit, so may not have completely
reflected my level of understanding it.
Let me try to explain it better. There are two separate issues: name
resolution, and class layout. Name resolution is relatively simple to
solve; it's the layout that is the thorny solution. ok.

When you layout a class in memory, it is easiest to think of it as an
array of "slots" for actual field storage (think C structs if you're
used to that). Class layout is typically done by laying out the
superclass first and then using extra slots for the subclass.

While C++ doesn't (or didn't) always keep RTTI (for the eventual
by-reader: "run time type info"), Java *always* knows of what class
any instance really is, so most of the price necessary for multi-
inheritence is already paid for in Java-world.
Multiple inheritance throws a wrinkle into this scheme because of the
diamond problem. If D inherits from B and C, which both inherit from A,
then the standard way of laying out the class yields two copies of A.

That would have been my preferred MI approach for Java...

There are of course problems to solve, but they can be solved by
definitions: "if not unique, pick the first one along the super-class
list that qualifies". If a particular grandsuperclass is wanted, there
would need to be some qualification syntax for it.

I'm not going to break my head on implementation details of how
a particular field would be located in the slots. I'd assume that
even if there is some extra offset that would need to be checked,
then the hotspot JIT compiler would surely do a good job on eliding
it for all "is-primarily-a" relations.
There are certainly cases where multiple inheritance is warranted;
[ ... and cases, where ppl *think* it is, but isn't. ]
This surely wouldn't be the only feature that could be used or misused...
 
V

Volker Borchert

Robert said:
What does dynamic_cast in C++ do differently? As far as I am informed
it involves a type check as well. I mean, you can have the same in Java
by catching the exception (which is what you need to do in C++ IIRC).

No you don't, that is exactly the raison d'etre of dynamic_cast.

if (o instanceof C) {
final C c = (C) o;
// do something with c
}

does _two_ typechecks

final C c = dynamic_cast<C>(o);
if (c != null) {
// do something with c
}

would do _one_ typecheck and a cheap IFNONNULL
Since Foo inherits Object you could as well make Foo<T> inherit
WeakReference<T> avoiding the additional bytes per instance.

Sorry said:
I find it difficult to discuss this without knowing more about your
requirements and situation. But then again, maybe you don't want or
cannot disclose that.

One example, for a Cache<Key,Value> that supports mixed flavor Value
references, and a subclass which additionally has entries doubly
linked, somewhat similar to LinkedHashMap, I want

AbstractEntry<K,V> WeakReference<V>
| |
| |
/ \ |
+---------------+---+----------+-------------+ |
| | | |
| | | |
AbstractLinkedEntry<K,V> StrongEntry<K,V> | |
| | | |
| | | |
+---------------+---+----------+ +--+---+--+
| \ / \ /
| | |
| | |
| StrongLinkedEntry<K,V> WeakEntry<K,V>
| |
| |
+------------------------------+---+--------------+
\ /
|
|
WeakLinkedEntry<K,V>


(soft flavor omitted for safe of readability)
 
V

Volker Borchert

Joshua said:
And you think multiple inheritance has no costs?
No.

2. Offsets to a particular named field are dependent on object
instances, not on classes/class hierarchies.
3. Casting up or down a tree requires recomputing offsets on a
per-object instance basis, not a per-class basis.

If they had spent the time wasted on yet another GUI framwork on
these... After all, that's what JIT is good at, isn't it?
inheritance, at least as
Java is concerned, is meant to model an "is-a" relationship, not a
"has-a" relationship.

I know. And IMO this holds true generally, not only for Java.

I admit that in my Cache<K,V> example you could argue that the Entry
_has_ a WeakReference<V> rather than _being_ one. But then, being-a
WeakReference said:
Since inheritance is an indelible part of the
public contract of a class in Java,

Another shortcoming of Java, but not necessarily of the JVM - lack of
nonpublic inheritance.
Let the JIT and the optimizer work through the overhead;

They can't work through the extra memory required for the extra object
needed for aggregation.
premature optimization, after all, is the root of all evil.

+1
 
R

Robert Klemme

No you don't, that is exactly the raison d'etre of dynamic_cast.

if (o instanceof C) {
final C c = (C) o;
// do something with c
}

does _two_ typechecks

final C c = dynamic_cast<C>(o);
if (c != null) {
// do something with c
}

would do _one_ typecheck and a cheap IFNONNULL

I was only aware of the exception thrown from dynamic_cast - but that is
only the case for reference types. Thanks for the update!

I have no idea how these really compare performance wise: there is JIT
and then I do not know how expensive the type check is really. The best
would be to make _realistic_ measurements of the type checking overhead.
One example, for a Cache<Key,Value> that supports mixed flavor Value
references, and a subclass which additionally has entries doubly
linked, somewhat similar to LinkedHashMap, I want

AbstractEntry<K,V> WeakReference<V>
| |
| |
/ \ |
+---------------+---+----------+-------------+ |
| | | |
| | | |
AbstractLinkedEntry<K,V> StrongEntry<K,V> | |
| | | |
| | | |
+---------------+---+----------+ +--+---+--+
| \ / \ /
| | |
| | |
| StrongLinkedEntry<K,V> WeakEntry<K,V>
| |
| |
+------------------------------+---+--------------+
\ /
|
|
WeakLinkedEntry<K,V>


(soft flavor omitted for safe of readability)

So if I understand your diagram correctly you want to mix two
implementation aspects
1. weak or strong referencing
2. linked and not linked entries (probably for LRU scheme)

Given what I know so far I'd still do it as suggested earlier: use an
Object field to either reference the item or a WeakReference. If that
overhead compared to the cached keys and values is significant then,
yes, that might be something to worry about. If on the other hand you
are caching contents of Icon files I wouldn't think about that overhead.

The linked entry class would then be a sub class of the simple reference
and contain everything necessary for managing the linked list of entries.

If a single cache has uniform entries then you can avoid the type
checking with instanceof and just cast. The algorithmic part could even
be abstracted away via an interface referenced by the cache instance.
For the strong reference type you would end up with the single cast that
appears outside the cache when the reference is assigned a variable of a
specific type; type erasure will make all V things Object inside the cache.

Now we just need to know what you want to do with this and why you need
to be asynchronously notified of GC'ed entries. :)

Kind regards

robert
 
J

Joshua Cranmer ðŸ§

While C++ doesn't (or didn't) always keep RTTI (for the eventual
by-reader: "run time type info"), Java *always* knows of what class
any instance really is, so most of the price necessary for multi-
inheritence is already paid for in Java-world.

RTTI doesn't enter into the picture. The problems it solves are
downcasts and reflection. The problem is doing an upcast between two
classes, neither of which are the most derived type of a class. Even in
C++, the most derived type is easy to find (it's the vtable pointer).
That would have been my preferred MI approach for Java...

This approach means that following statement holds:
D d = new D();
(A)(B)d != (A)(C)d;

The point is that the "identity" of casting an object to a superclass
depends on the path through which you do it.
I'm not going to break my head on implementation details of how
a particular field would be located in the slots. I'd assume that
even if there is some extra offset that would need to be checked,
then the hotspot JIT compiler would surely do a good job on eliding
it for all "is-primarily-a" relations.

This requires shape analysis and/or devirtualization to be able to
remove the extra level of indirection. I don't know how well the JVM
JITs are at doing these nowadays, but when Java was first developed, the
state of the art in terms of research was pretty much not there to
eliminate the indirection often enough.
 
J

Joshua Cranmer ðŸ§

No you don't, that is exactly the raison d'etre of dynamic_cast.

if (o instanceof C) {
final C c = (C) o;
// do something with c
}

does _two_ typechecks

For any optimizer that can do basic CSE (which is any optimizer that
anyone would not be ashamed to make publicly available), the second
typecheck can be trivially eliminated.
 
R

Robert Klemme

Am Freitag, 29. November 2013 12:05:37 UTC+1 schrieb Silvio:


AFAIK Java8 lambdas are somewhat less verbose wrappers around anonymous inner classes. When I saw that closure vars inside Java8 lambdas are final my first thought was that obviously Java is from now on on maintenace and that's it. The language Java won't evolve any more. I guess things will just stay like that from now on as the case for Cobol or something. The focus will be on tools, framewors, systems, etc. as it already has been for a whileI fear ...

The problem with "real" closures (i.e. the ones you can write to) is that they require a dramtic change in the underlying mechanics: you need to keep stack frames around for longer than a method call. Having that in Java would be really nice but the potential for impact on the huge basis of existing code is severe. I personally like the simplified anonymous classes approach because it is a pragmatic step forward: you get to use anonymous functions with far less visual overhead than before.

Kind regards

robert
 
S

Silvio

The problem with "real" closures (i.e. the ones you can write to) is that they require a dramtic change in the underlying mechanics: you need to keep stack frames around for longer than a method call. Having that in Java would be really nice but the potential for impact on the huge basis of existing code is severe. I personally like the simplified anonymous classes approach because it is a pragmatic step forward: you get to use anonymous functions with far less visual overhead than before.

Kind regards

robert

Not necessarily. Local variables that are written to from closures in
their scope can also be given an extra level of indirection by mutably
wrapping them in an object. All access to the variable is then done
through the wrappers which can be copied into closures just like values
are copied in the read-only scenario.
 
S

Silvio

Not necessarily. Local variables that are written to from closures in
their scope can also be given an extra level of indirection by mutably
wrapping them in an object. All access to the variable is then done
through the wrappers which can be copied into closures just like values
are copied in the read-only scenario.

BTW: that is how Scala does it so it is not a very experimental approach
anymore. To many Java purists the thought that a primitive might become
a full object may seem horrendous but if you think about it autoboxing
does the same thing, even at the risk of repeatedly boxing the same
primirive over and over...
 
R

Robert Klemme

Yes, true. That's an approach I had not thought of. The only downside
I can see immediately is that you may pay the overhead for the
additional indirection even if the closure is not used (e.g. because it
is not in the branch of code actually executed). One can even optimize
the approach by creating a single instance carrying the local state so
there is not one object per referred local variable but one per closure
creation. But I guess that overhead is as acceptable as this:
BTW: that is how Scala does it so it is not a very experimental approach
anymore. To many Java purists the thought that a primitive might become
a full object may seem horrendous but if you think about it autoboxing
does the same thing, even at the risk of repeatedly boxing the same
primirive over and over...

Another thing which might not be that nice is that instance variables
will be accessed differently, i.e. through 'this'. So there is one
access path for local variables introducing a synthetic indirection and
another one going through 'this'.

Still, I don't see any hard reasons that speak against doing real
closures. Maybe it's just the pragmatic way, i.e. not enough resources
to do the full thing (think of the delays of Java 8 we have seen
already) and also that gives them the chance to learn how people use the
new feature and maybe improve it before going full circle.

Kind regards

robert
 
S

Silvio

Yes, true. That's an approach I had not thought of. The only downside
I can see immediately is that you may pay the overhead for the
additional indirection even if the closure is not used (e.g. because it
is not in the branch of code actually executed). One can even optimize
the approach by creating a single instance carrying the local state so
there is not one object per referred local variable but one per closure
creation. But I guess that overhead is as acceptable as this:

Yes, that is a possible optimization. But, the combined object approach
can become very complex when multiple closures are in play, which is
very common once real closures are available.
Another thing which might not be that nice is that instance variables
will be accessed differently, i.e. through 'this'. So there is one
access path for local variables introducing a synthetic indirection and
another one going through 'this'.

That is correct and it is also a gotcha with Scala closures. Not that it
is something you have to consider often but in very rare cases it boils
up and may force one to copy something into a local temporary.
Still, I don't see any hard reasons that speak against doing real
closures. Maybe it's just the pragmatic way, i.e. not enough resources
to do the full thing (think of the delays of Java 8 we have seen
already) and also that gives them the chance to learn how people use the
new feature and maybe improve it before going full circle.

Kind regards

robert

I also suspect the resources part was a factor. That and the fact that
it was hard enough already to reach consensus on the proposal(s).

Cheers,

Silvio
 
M

markspace

Still, I don't see any hard reasons that speak against doing real
closures. Maybe it's just the pragmatic way, i.e. not enough resources

Have you read any of the discussion on the lambda-dev list? Brain Goetz
was dead-set against "real" closures. He insisted on several occasions
that "real" closures were broken and would not have been designed the
way they were if computer science had known what they know today.

The main issue iirc was that he wanted the lambdas to be immutable. If
you go back and modify objects or fields by closing over them, then you
no longer have an immutable operation. Whether the risk was thread
safty and optimization, or programmer misuse, or something else, I don't
recall off hand, but he was pretty adamant it was a dumb idea.
 
S

Silvio

Have you read any of the discussion on the lambda-dev list? Brain Goetz
was dead-set against "real" closures. He insisted on several occasions
that "real" closures were broken and would not have been designed the
way they were if computer science had known what they know today.

The main issue iirc was that he wanted the lambdas to be immutable. If
you go back and modify objects or fields by closing over them, then you
no longer have an immutable operation. Whether the risk was thread
safty and optimization, or programmer misuse, or something else, I don't
recall off hand, but he was pretty adamant it was a dumb idea.

Yes, and it was not a particularly strong argument that he made.
Closures are no different from any "normal" piece of code and there is
no argument whatsoever to restrict their abilities because of "thread
safeness" considerations. There is all but consensus that "computer
science" in retrospect would have done anything different, let alone
make the choices they made for Java.

Immutability and thread-safeness are very important considerations and
while Scala heavily emphasizes on using immutable container classes in
its standard library other languages like Haskell reject mutability as a
whole. I see value in both approaches. There are heaps of arguments that
can be made in this area but they don't concern closures in particular.
 
S

Saxo

Am Dienstag, 3. Dezember 2013 03:07:31 UTC+1 schrieb markspace:
The main issue iirc was that he wanted the lambdas to be immutable. If

you go back and modify objects or fields by closing over them, then you

no longer have an immutable operation. Whether the risk was thread

safty and optimization, or programmer misuse, or something else, I don't

recall off hand, but he was pretty adamant it was a dumb idea.

You can easily brake this using the single element array trick:

int sumArray[] = new int[] { 0 };
ints.forEach(i -> {sumArray[0] += i;});
println(sumArray[0]);

I wonder what he value is if it's that easily broken ...
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top