John Rose is a JVM architect at Oracle who publishes design notes on value types, invokedynamic, and Project Valhalla.
HN links his papers as the main references for Java value types and the parametric VM, and for debates about what Valhalla will deliver.
> It is an identity you can't get rid of, and which is often misused, or accidentally used, to create a lot of complexity. No, you can't override '==' in Java and other OO languages. You can override .equals() which defaults to ==.
Using Java as an example to prove something about OO isn't very useful IMO. It'd be like holding up rust as an example for how functional languages do things - yes, it's fairly functional but hardly canonical.
In ruby, for example, you can indeed override ==.
> You mentioned it yourself: structs or records, a.k.a value semantics. Here is a resource from people with more authority (esp. Guy Steele) http://cr.openjdk.java.net/~jrose/values/values-0.html
I think you're conflating structural equality on the one hand, and reference semantics on the other. The two aren't related in real OO. For example, here's a class that defines equality the same way an ML record might:
class Value
def ==(other)
if other.class != self.class then
return false
end
self.instance_variables.each do |v|
if instance_variable_get(v) != other.instance_variable_get(v) then
return false
end
end
return true
end
end
Then we can use it to define a simple point class:
class Point < Value
def initialize(x, y)
@x, @y = x, y
end
end
And there you go - structural equality, but still passed as references.
Point.new(0, 0) == Point.new(1, 2) => false
Point.new(0, 0)…
I was wondering about this very issue the other day, and wrote a little C program to try something similar. I tried three things:
1. Create an array of random ints, (sort them, not that it matters), and then time how long it takes to compute the sum.
2. Create an array of ints (data) and an array of int* (ptr). Set ptr[i] to point to data[i]. Scan the data through the ptr array and compute the sum. Obviously there is very good locality of access to both arrays.
3. Same as #2, but first sort the ptr array so that * ptr[i] <= * ptr[i+1]. There will be good locality on ptr, not so much on data.
For arrays of size 10M, #1 and #2 cost about the same, and #3 was 5x slower. (This was on my MacBook Pro, using XCode.)
But my main point here is about Java, which has been my main language for many years. I don't care (much) about its verbosity, its lack of closures, etc. I can get my work done. But working with large heaps has proven very problematic, both in avoiding GC overheard, and in the effects of non-locality.
While you can have both true int arrays and Integer arrays, the same is not true of non-primitive types. There is no way to have anything like an array of structs, with the same compact representation, as you can have in C. There are benchmarks showing that Java and C are competitive in performance. But C/C++ can be used to preserve locality for arrays of structs and objects in ways that Java cannot. Java has to be slower in these situations.
There are well-known…
I believe this is because of compatibility concerns when types migrate from object to value or vice-versa. The linked doc[0] says "operations can differ between value and reference types [...] This means that, if we are to support migration, we need a way for legacy classfiles to make a local decision to treat a given type as a reference type".
It is agreed in general that whether something is a value type is a definition-side attribute. However if code was compiled expecting the Optional value (e.g. does an ==) but it is now an Optional object, what happens? The identity check is different than the value-equals one. The doc goes into more detail on some differing things here.
I haven't studied the arguments or read the doc fully, but I'd say if you make it where it can only migrate one direction, object types to value types, you can solve the two big issues of equality (equals() is intercepted by the JVM for value types to imply ==) and nullability (value is never null, == null becomes always false). There may be other issues with hashCode, use as mutex in synchronized block, etc.
Maybe another approach is to have the value type implement "ObjectLike" if it wants to exist in the L-world. There needs to be a separation between what an "object type" is and what an "object type" can do. The latter causes migration concerns, the former is just about state and copying/reference semantics.
0 - http://cr.openjdk.java.net/~jrose/values/value-type-hygiene.html
Arithmetic operations on numbers are not polymorphic, but polymorphism has nothing to do with static typing per se. You're being confused here by the special treatment the JVM gives to primitives, special treatment that was needed to avoid the boxing/unboxing costs, but that's a separate discussion and note that hidden boxing/unboxing costs can also happen in Scala, which treats numbers as Objects.
Disregarding primitives, the JVM doesn't give a crap about what types you push/pop the stack or what values you return.
invokeDynamic is nothing more than an invokeVirtual or maybe an invokeInterface, with the difference that the actual method lookup logic (specific to the Java language) is overridden by your own logic, otherwise it's subject to the same optimizations that the JVM is able to perform on virtual method calls, like code inlining:
http://cr.openjdk.java.net/~jrose/pres/200910-VMIL.pdf
> ... because the JVM was too statically typed at its core
Nice hand-waving of an argument, by throwing a useless Wikipedia link in there as some kind of appeal to authority.
I can do that too ... the core of the JVM (the HotSpot introduced in 1.4) is actually based on Strongtalk, a Smaltalk implementation that used optional-typing for type-safety, but not for performance:
http://strongtalk.org/ + http://en.wikipedia.org/wiki/HotSpot#History
Without easy access to SIMD or value types (and JNI does not count as easy), the JVM's performance for regular computations on large datasets is still pretty lacking. Quoting http://cr.openjdk.java.net/~jrose/values/values-0.html, "Numeric types like complex numbers, extended-precision or unsigned integers, and decimal types are widely useful but can only be approximated (to the detriment of type safety and/or performance) by primitives or object classes."
And the JVM's costs in terms of deployment and startup times aren't very attractive for scientific workloads. You wouldn't gain as much in a migration from Python or Matlab to Java (or any other JVM language) as you would to C++ (or increasingly Julia), since you'll likely still need access to the same native libraries and JNI is kind of a pain.
Java still uses a linked list chaining until a threshold is met (8 currently) which it then switches the bucket to a tree. I wish java could switch to open address hashing, but without value types it wouldn't help much (there's work on adding them: http://cr.openjdk.java.net/~jrose/values/values-0.html).
Linear probing would likely be better than cuckoo hashing from a cpu cache perspective (all locations closer in memory). There are probing schemes which result in low number of checks and allow for higher loading factors similar to cuckoo hashing (e.g. robin hood hashing. See http://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/)