There actually is a really good compiler reason to not allow subtyping concrete types, and it's one of the main reasons Julia code is faster than languages like Java. If you allow concrete types to have subtypes, when you see `Vector{Int}` (List<Integer> in Java), the compiler can't tell what the memory layout of the element type will be. As a result, pretty much all objects end up boxed which destroys locality and removes all possibility for vectorization. In Julia, the compiler knows the memory layout of any insatiable type at compile time, so it can much more aggressively remove pointers (or even just decide that an object will exist in registers rather than allocating memory for it).
That's not a result of `int` not being subtyped. It's a result of forcing reference semantics on everything (which is something most languages unfortunately do).
C++ doesn't need this kind of restriction because it makes the difference between references and values explicit, so it's actually possible to explicitly ask for a vector of values, rather than only being able to ask for a vector of references and artificially restricting the language so that it can be optimised to a vector of values.
(Yeah I know you can't derive from int in C++ but that's for other reasons.)
Julia also has reference semantics for everything. That said, it turns out that if you make it idiomatic to create immutable structs, the difference between reference semantics and copy semantics disappears, and lets the compiler pick the faster one (which fixes a common performance bug in C++ where users end up copying a ton of data around by accidentally copying a bunch of data)
Yeah I always thought that "make everything immutable!" was partly - maybe even mostly - a workaround for the fact that it's so hard to copy data in most languages.
I agree C++ makes it too easy to accidentally copy data, but at least you can copy data!
I don’t see how is it inherent to subtyping, but language semantics. For example Java will likely add 2 new types of class that will loose identity - at that point Integer (and other, struct-like data) will be optimizable by inlining into the surrounding data structure.
The point is not limited to Int though. Say you have this struct:
struct MyStruct
a::Int64
b::Float64
end
Since the size of each field is known (8 bytes), the size of the whole struct, and thus the size of each instance of that struct, is known - 16 bytes. This is crucial information for inlining, loop unrolling, copy elision, deciding which register(s) to place the object in, getting rid of indirections through pointers.. If you'd allow to subtype MyStruct, the compiler wouldn't be able to know ahead of time what the size of a MyStruct object is and would either have to box every access (introducing pointers which have to be chased on access) or defer A LOT of work to runtime. With boxing, you almost immediately lose lots of opportunities to SIMD or otherwise optimize your code, because you have to keep a whole lot of extra type information around at runtime that's just not necessary when all your objects in a e.g. Vector{MyStruct} have the same size anyway.
right. The Julia design is to say that `final` was a good idea and that everything should have it (Especially since OOP people now say to prefer composition to inheritance anyway).
you misunderstand both Java generics and Java primitive types.
1. the JVM does not support generics as a language construct, and are simply a compiler feature of Java
2. the JVM and Java differentiates primitive and reference types, Integer being a reference (boxing) type of the primitive type int. And, generics do not support primitive types anyway.
The JVM implementations are incredible pieces of machinery, and even if they did not pack or stride their primitive arrays -- which they absolutely do -- the JVM & compilers will handle an incredible amount of runtime memory layout and packaging.