1. Julia had the benefit of clever design, as well as not needing to support legacy interfaces that are difficult to optimise. Python has had similar problems (which needs to support all the different low-level interfaces), whereas JavaScript, which provides fewer ways to "mess with the internals", now has several high-performance engines.
4. Although Julia does provide the ability for type annotation, it is actually rather rarely necessary, due again to good language design which makes automatic type inference feasible.
5. Julia originally discouraged use of vectorised operations for this reason. But now we've gone the other way, with special "dot" syntax which avoids allocating intermediate arrays. See https://julialang.org/blog/2017/01/moredots for more details.
When you want to perform a matrix operation, like multiplication, that for efficiency needs to operate on a plain array, here's what you do. First, if the override map is empty, you can use the array as it is. Otherwise, you make a new copy of the array, then iterate through the override map, storing each value in the array at the specified coordinates. Then you write the pointer to the copied and updated array into the array slot of the original pair. Yes, this modifies a nominally immutable object, but critically, it does so in a way that doesn't change its semantics. Finally you write, into the override-map slot of the original pair, a pointer to the canonical empty map. Now you can pass the array to your matrix multiply routine, or whatever.
The two writes don't even require locking in a multithreaded situation, though on many architectures there will need to be a memory barrier instruction between them, so another thread can't read the two writes in the wrong order. Given that, the worst that can happen is that another thread sees the updated array but the original override map, whereupon it will make a redundant copy of the array and redundantly apply the updates to it, doing a little extra work but producing an identical array.
So -- just thinking out loud here -- in order to use a design like the one I've proposed, you would have to make the "flatten" operation user-visible; it would look like a type conversion from a functional matrix to a bare array. Then the user's algorithm could run on the array, and once they were out of the hot section, they could, if they so chose, convert it back to a functional matrix.
If I personally were going to write Julia code, I think I would like things to work this way. Not only am I accustomed to functional semantics, and generally prefer it, but I'm also used to switching modes (even writing mixed algorithms in which some collections are functional and some are imperative). But I can see the argument that that might be a little too much complexity for your target audience.
Understandable. The approach and philosophy is very different, however. Julia feels much more like dynamically typed C++ than it does like Python with heterogeneous arrays added on.
> you would have to make the "flatten" operation user-visible
This seems like it would be a method of the `convert` function. Built-in types are barely special at all in Julia, so you could – and people have [1] – make purely function analogues of any data structures you like and use all the usual operations on them. One can even allow mutation when necessary, but just make the non-mutating operations sufficiently efficient that mutation is rarely necessary.
A fair amount of mathematical code does typically work in two distinct phases:
1. construction/mutation phase – vectors and matrices are constructed and mutated to setup the problem;
2. computation phase – vectors and matrices are used in computations, typically in a purely functional manner, where each operation creates a new objects (e.g. `Z = sqrtm(2X + Y^2)`).
Unfortunately, purely functional collections don't help much with either of these phases: the first phase is inherently full of mutation and typically quite local, so the drawbacks of mutation are limited; the second phase is already inherently functional but doesn't need functional data structures. What you really need in the second phase is escape analysis that is clever enough to figure out that it can reuse temporaries.
[1] https://github.com/JuliaCollections/FunctionalCollections.jl
I think it is a little disingenuous to say that a Julia programmer does not have to worry about types. Type inference alleviates many burdens, but correct typing of arguments is essential (and hidden promotions or casting can kill performance). So while you can write correct programs easily, for efficient programs you end up worrying about this quite a lot.
I don't know the specifics of Julia's GC, but this seems a strange thing to say in 2017. Douglas Crosher's conservative generational collector for CMUCL (also used in SBCL AFAIK) supports C interoperation and is entirely adequate for handling the extra garbage that (admittedly) is generated when using functional collections. I don't recall exactly when he wrote that collector, but it must have been 20 years ago at least. It would be strange if Julia weren't using something at least as sophisticated.
I didn't say Julia programmers don't have to worry about types, my comment was only about type annotations, though they are obviously related. Perhaps a better way to phrase it is: by worrying a little bit about types, you can generally avoid the need for type annotations. Hopefully as the tooling around the language matures, we can reduce the amount of worrying required.