There’s also some unfortunate choices Java made like standardizing one specific semantics for reproducible floating point code. That’s unfortunate because adjusting for native SIMD widths sacrifices reproducibility but improves both accuracy and speed. The only choice if you want perfect reproducibility on all hardware that Java supports is the worst performance model with the worst accuracy.
There’s also the fact that Java integers are 32-bit and Java arrays are limited to 2GB, which was reasonable when Java was designed, but is pretty limiting for modern numerical computing.
I also think that the JVM object model is quite limiting for numerical computing. They still don’t support value types, but value types are precisely what you want to represent efficient numerical values like complex numbers, or quaternions or rationals, and so on. Java forces all user-defined types to be heap-allocated reference types. Julia solves this by defaulting to immutable structures, which is exactly what you want for numerical values: the semantics are still referential (if you can’t mutate you can’t distinguish value semantics from reference semantics), you just can’t change values, which is exactly how you want numbers to behave (you don’t want to be able to change the value of 2).
Lack of value types in Java also makes memory management unnecessarily challenging. You can’t make a user-defined type with an efficient C-compatible array layout in Java. Because the objects are references, so the array is an array of pointers to individual heap-allocated objects. The ability to subtype classes forces that, but even with final classes, the ability to mutate objects also forces it, since pulling an object reference out of an array and modifying it is required to modify the object in the array (reference semantics), and that’s incompatible with the inline array layout.
And finally, this same lack of value types puts a LOT of pressure on the garbage collector.
This is mostly true, but the primitives are value types and you can get some things done with them. (Not enough to make Java good for these use cases, no.) I.e. write float[] instead of Float[] and you have a contiguously allocated region of memory that can be efficiently accessed.
What do you do in a dynamic language where no such separation can be enforced? Or in a static language where you just don’t want that kind of ugly anti-generic bifurcation of your type system? The classic static PL answer is to just make all objects have reference behavior. (Which is the approach Java takes with a pragmatic ugly exception of primitive types.) This is the approach that ML and its derivatives take, but which is why they’re unsuitable for numerical computing—in ML, Haskell, etc. objects have “uniform representation” which means that all objects are represented as pointers (integers are usually represented as special invalid pointers for efficiency), including floating point values. In other words an array of floats is represented in ML et al. as an array of pointers to individual heap-allocated, boxed floats. That makes them incompatible with BLAS, LAPACK, FFTW, and just generally makes float arrays inefficient.
So what does Julia do? Instead of having mutable value and reference types, which have observably different semantics, it has only reference types but allows—and even defaults to—immutable reference types. Why does this help? Because immutable reference types have all the performance and memory benefits of value types! Yet they still have reference-compatible language semantics, because there’s no way to distinguish reference from value semantics without mutation. In other words immutable structs can be references as far as the language semantics are concerned but values as far as the compiler and interop are concerned. And you can even recover efficient mutation whenever the compiler can see that you’re just replacing a value with a slightly modified copy—and compilers are great at that kind of optimization. So all you give up is the ability to mutate your value types—which you don’t even want to allow for must numeric types anyway—and which you can simulate by replacement with modification. This seems like a really good trade off and it’s a little surprising that more languages don’t make it.
The Julia approach is indeed elegant. An in-between position taken by F# is statistically resolved type parameters, where type safety is maintained and semantics largely preserved by constraints on members. It's a pain to write but trivial to consume though errors can be obstruse. More type level flexibility on the CLR (which seems to be planned) will further improve things when it comes to generic programming.
Python was written with people like scientists in mind. Professionals write fast C libraries and then people who know just enough to get by use python to glue it all together.
Having just taught a bunch of scientists Python, I'm skeptical of this.
Stuff that is obvious to me, but really hard to explain to coding beginners:
* why `len(list)` but `str.join(...)`? I.e. why are there two ways to call functions?
* why does `[]` do so many different things? List comprehensions, list construction, subsetting, sometimes it secretly calls a method....
* why in the name of god is the first row called 0?
* and then why does [0:3] mean rows 1, 2 and 3, not rows 0, 1, 2, 3 or 1, 2, 3, 4?
* ... except in pandas where sometimes [foo:bar] includes bar...
* in general, why is pandas so damn complex?
I thought it'd be easier to teach than R, but I'm not quite so certain now.
- plenty of scientists write good code; I think the "scientist can't code" meme is harmful.
- most of the people that write PyTorch code aren't necessarily scientists - they're software developers (data scientists, ML engineers, research engineers, whatever title - but their main job is to write code)
> Python was written with people like scientists in mind.
Python had nothing to do with scientists when it started. It was written to be readable and easy to use and it started as a hobby project. It existed for over 15 years until the data science / ML ecosystem started growing around it.
But you can just write a simple 20 line Python script to do some data mangling, no project with 30 IDE files required.
Visual J++, Visual Cafe and JBuilder were the main ones but not everyone was eager to buy them, while the JDK was free beer.
Python doesn’t compile to machine instructions either and there’s nothing that prevents GPU access from Java. In fact I’d bet in many cases pure Java beats Python + C library though it obviously depends on how much has to be written in pure python.
And the VSCode extension isn't quite IDE level, but that's just a matter of resources.
In future it would be great to mashup something like repl.it with Jupyter notebooks, maybe with Medium-style wysiwyg. That might really win.
See this talk for examples: https://www.youtube.com/watch?v=kc9HwsxE1OY
> A language must compile to efficient code, and we will add restrictions to the language (type stability) to make sure this is possible.
> A language must allow post facto extensibility (multiple dispatch), and we will organize the ecosystem around JIT compilation to make this possible.
> The combination of these two features gives you a system that has dynamic language level flexibility (because you have extensibility) but static language level performance (because you have efficient code)
Given those constraints, the first language that comes to mind is Java. Why is Java basically not a player in the scientific-computing game?