Julia is designed for advanced numerical computing and Python isn't. The metaprogramming affordances needed for AD are much better developed in Julia than they ever will be in Python. And let's not forget the immense utility of multiple dispatch in Julia, another feature Python will probably never have. So it's not surprising that Julia is simply way more capable.
Python is just more approachable and natural to people. Julia should learn from that
But personally I find OOP ugly and unnatural, and Julia's model elegant and natural. And far more powerful - Julia programmers are using multiple dispatch to build out scientific computing to a sophistication not seen in any other language.
It might not be your cup of tea if you need to see object.method() in your code, but if you're more mentally flexible and want to build the next generation of technical computing tools, Julia is the place to be right now.
This definitely fits with my experience. It took me quite a while to really "get" dispatch-oriented programming as a paradigm, but once I started to get it there was no going back.
I’ve tried it for close to a year and the ergonomics still felt off, it reminds me of how the scala crowd talked about functional programming, and we’ve seen how that turned out.
I hear this from a lot of people that try Julia and yet the Julia crowds answer is always that they are dumb. Sounds a lot like the scala crowd…
Julia is far ahead in affordances to write fancy technical code and fairly behind in simple things, like standard affordances to write more ordinary code, or the ability to quickly load in data and make a plot.
I just think it's a misdiagnosis to blame multiple dispatch for this issue. It's much more about the Julia community prioritizing the needs of their target market.
The reason why Julia is fast is because automatic function specialization to concrete dispatches gives type-grounded functions which allows the complete optimization to occur on high-level looking code (see https://arxiv.org/abs/2109.01950 for type-theoretic proofs). It's basically a combination of (1) define a type system in a way that allows for type-grounded functions and compile-time shape inference (shape as in, byte structure of the structs), (2) define a multiple dispatch system with automatic function specialization on concrete types, (3) have a typed IR which proves and devirtualizes all dispatches before hitting the LLVM JIT. If you simply slap the LLVM JIT on random code, you will not get that performance. But now because multiple dispatch is fundamental to performance in the language, the rest of the "game" for the language is how to design an ergonomic language around this feature and how to teach people to use it effectively as a problem solving tool.
You actually see something similar going on in the world of Jax. With Jax, you need to be able to perform abstract interpretation to the Jax IR. In order for this to be possible with the interpreters Jax has, the functions that are being interpreted need to always have the same computational graph for the same inputs, i.e. they need to be pure functions. This is why Jax is built on functional programming paradigms. It would be similarly uncharitable to say the reason why Jax does not embrace OO is because the developers just love functional programming: the programming paradigm choice clearly falls out of what the tools needs to do.
It remains to be seen if Jax is the tool that makes more people finally embrace functional programming styles, or if enough people see pervasive performance necessary enough to change to the multiple dispatch paradigm of Julia. But what is clear is that tools that are moving away from OOP are not doing so arbitrarily, it's all about whether doing so is beneficial enough to justify the change.