What do we have to attract C++ or other languages' power users to try Julia?

I know threads about increasing Julia’s adoption among the general programmer population have been done to death. I’m not making another one of those threads. Instead, this thread would focus on the top 1% of programmers. Moreover, to maintain the focus, I would like this thread to be about what Julia has that would attract these power users, not some hypothetical “What could we do?”.

One of Julia’s main strengths is easy high-performance computing. However, imagine you were a C++ expert at the moment.

Julia offers generic and metaprogramming. Well, C++ with templates, decltype, auto, etc, offers an extremely powerful generic and metaprogramming capability, and C++ experts would not need them. Moreover, while people might argue that not many people master these features, they might argue that you only need to know basic C++ to use the libraries they wrote, even if they used these advanced features behind the scenes.

Julia might make it easier to write high-performance code, but for C++ experts, they are already really comfortable with all the nooks and crannies of C++ and how to squeeze the performance through compiler options, intrinsics, etc. They would not find Julia very useful in this regard, especially if they found out that Julia is only 95% as fast as C++, if not slower.

Julia could also boast its package management and tools such as physics simulation, etc, but it’s not like the C++ folks don’t have their own, and even if it’s harder to use for normal people, the first point is that a lot of these things (like finite element analysis) are already hard no matter the language. The second point is that if you’re well-versed in high-performance computing, you’re probably already well-versed in C++. The final point is that a lot of times, the interface is no code at all, and then, the point that it’s easier to use these packages is moot.

What do we have to turn the tide?

As much as we love Julia over languages like C++, Java, maybe Rust, etc, we have to acknowledge that these languages, in the right hands, are very powerful. It would take some serious work to convince them that Julia is a good language. Micro-benchmark tricks and other gimmicks probably don’t work because it would probably take them <10 hours to get what they wanted in their languages, even if it’s easier in Julia. You need something where it’s pretty much not viable for them to make it work in their languages. That is pretty hard because, again, those languages, in the right hands, are very powerful. Even if the parallel features in those languages are harder to use, for power users, they could figure out ways to make them work.

I myself have coded a bunch of gimmicks that would be really hard for me to do in other languages, but again, that assumes my perspective. We’re talking about power users here.

So, back to the question. What do we have?

1 Like

Creating a programming language takes a lot of work, and people wouldn’t do it if they didn’t believe it is good or even the best (at something). So I’d argue that almost all programming languages are ‘good’. If an expert programmer in some other language refuses to accept even this (after brief argumentation), trying to convince them to try out (cf. title) or switch to Julia is a lost cause.

Sure, but if you can demonstrate that what takes an expert 10 hours in C++, only requires 1 hour of development time in Julia for almost the same performance, then that’s a solid argument. This is the same argument as advocates for Python would use. The fact you can easily experiment in the REPL is a powerful selling point. And in contrast to (pure) Python, Julia can be very performant. Hence, Julia gives you fast code fast. (Though to be more modest, the argument of solving the two language problem is more that you can quickly write prototype code in Julia, the essential parts of which you can then iterate upon (optimise internally, without switching language) to become very performant.)

4 Likes

I used to be a pretty competent C++ programmer. I am no longer that. While it was fun to spend entire weeks talking to labmates about how to micro-optimize some bitsets or other obscure code, I enjoy even more just doing using SomeLibrary and making things faster.

What do we have? I suggest you look around, Julia has a lot. A small list for you to ponder.

I have no idea what is a power user or “top 1% of programmers”. That doesn’t seem like a healthy way to think about adoption. I see university classes and labs adopt Julia and that tells me enough, that the next generation of programmers and scientists will be using it, and that’s good enough for me.

6 Likes

Julia has a number of technological limitations:

  1. Garbage collection - which does not allow you to stack data in memory as desired, thus making it difficult to write fast algorithms that depend on cache-friendly.
  2. heavy runtime - GC and the need to carry a compiler and environment make Julia unattractive for writing libraries (outside the Julia ecosystem) compared to C++ or Rust. For example, if I wrote scientific code in Rust, using PyO3 and jlrs I can easily bind to Python and Julia and share my code with my coworkers. If I want my code to work in production on big data, I just use plrs and make an extension for Postgresql. A lightweight runtime allows you to put and run your code anywhere.
  3. Dynamic dispatching - although convenient, it carries a performance penalty due to type checking and a barrier in interprocedural optimization. That’s why Julia will always be slower than static languages with Ahead-of-Time compilation. And you will never convince a C/C++, Rust, etc developer that Julia can run as fast as their languages.

There are also a number of aesthetic compromises in Julia:

  1. indexing from 1. This is not something you couldn’t get used to. However, this solution often causes annoying errors when serializing/deserializing data between peers in other languages.
  2. Lack of postfix notation as a language base. Often we know what data we want to work with, but we don’t know what function to use to do it. And for ordinary programmers who do not feel piety before mathematical notation it is often more pleasant to call a.sin().ln() than ln(sin(a)). After typing . the autocomplete will obligingly provide all available methods for working with the variable a. And it’s easier to read.
  3. I won’t talk about weak encapsulation and lack of static(stable) interfaces or their analogs.

As I see it, Julia was developed with Matlab in mind. That is, as a language for scientific calculations, not as a language for writing programs for scientific calculations. Therefore, adapting the language and technologies of Julia will require modifications in Julia 2.0. And these modifications may not be to the liking of the current community.

2 Likes

That’s not a compromise, that’s great.

19 Likes

What does this mean?

Is there a language that generally implements mathematical functions as member functions of numeric classes?

It’s not that there’s a lack of postfix notation, it’s just that it’s underpowered. Julia’s “postfix notation” is the pipe operator |>. The problem is that its right-hand-side must evaluate to a function, like [1,2,3] |> sum. If it’s a function call, it must return a function, etc. However, a more powerful pipe syntax could allow for code like:

  1. [3,2,1] |> sort(ascending=true), where the LHS is inserted as the first argument of the function call. With this rule, piping can be syntactically rewritten as sort([3,2,1], ascending=true). This is equivalent to your dot operator. Without this rule, you have to resort to lambdas like [3,2,1] |> (x -> sort(x, ascending=true)). This is ugly, doesn’t support autocomplete and is too much to type.
  2. [3,2,1] |> f("hello", _, 3.1415926), where underscores are to be replaced by the LHS to produce f("hello", [3,2,1], 3.1415926). The main question seems to be what to do when there are multiple underscores at various nesting levels like x |> g(f(3, _, _), 2, _)? Should f(3,_,_) become (a,b) -> f(3,a,b)? But it also kind of makes sense to make x |> g(f(3,_), 2, _) mean g(f(3,x), 2, x) instead of g(a->f(3,a), 2, x).

Unfortunately, there’s no consensus here, there are only Discourse threads and GitHub issues with people arguing over this.

Yes, Rust has syntax like this:

let num: f64 = 3.141;
println!("{}", num.log(10.0).sin());
1 Like

This is a long-standing dispute, similar to what number to start a natural series with, 1 or 0 =). Some algorithms are written more beautifully with index from 1, some from 0. For example, to work with a physical grid or time it is convenient to have a zero index where you can put values at zero distance or zero moment of time.

Natively it is implemented in Rust, Kotlin, Ruby. In Python, it is a standard API for numpy, jax, pandas etc, In other OOP languages this approach is widely used for organizing API libraries and is called method chaining/fluent interface.
And when I wrote about postfix notation I meant not only working with numbers, but in general working with all data types.

This seems to be a major misunderstanding. The compiler de-virtualizes dynamic dispatches via function specialization all of the time! This is like “the” feature of Julia, that dispatching is generally not dynamic after compiler optimizations and thus it has none of these overheads post-optimization for type-stable functions. It is actually a provably correct transformation as well with complete proofs!

18 Likes

I think I’m mildly aware of the dispute, it’s more that I find it annoying every time someone rehashes the debate, I even create stupid packages with non-sense indexing to make fun of it.

9 Likes

This is false: numpy.sin, numpy.log. numpy.ndarray has quite a few member methods, but not most of the mathematical functions in NumPy, or SciPy for that matter. C++ has cmath, Ruby has Math, Kotlin has math. Rust actually stands out here.

3 Likes

I admit, I’m wrong. In Kotlin we have extension methods like fun Double.sin() = sin(this) on our project, and I thought it was from the box. And in C++, if you use Eigen, it provides different writing options.

Just to add onto this, it’s quite easy to just test for yourself @Hexen if this is happening:

f(x::Int) = x + 1
f(x::Float64) = sin(2x + 1)

g(x::Int) = x^2 - 1
g(x::Float64) = sqrt(x) + 1
julia> code_llvm(Tuple{Int}; debuginfo=:none) do x
           f(g(x))
       end
; Function Signature: var"#5"(Int64)
define i64 @"julia_#5_4112"(i64 signext %"x::Int64") #0 {
top:
  %0 = mul i64 %"x::Int64", %"x::Int64"
  ret i64 %0
}

Here you can see that generic functions with multiple methods (2 methods each in the case of f and g, 191 methods in the case of +, and 72 methods in the case of ^) were all devirtualized, and interproceedural optimizations were applied in order to simplify the complied code down to just a self-multiply.

Julia has real limitations, but speed, devirtualization, and interproceedural optimizations are demonstrably not among of them.

7 Likes

Also curious about this one.

This is the one I agree the most. It is very hard to attract people with C++/Rust background interested in writing libraries for Python without a smooth static compilation experience in place.

2 Likes

What’s stupid about this one? I was going to refactor all code to use StarWars-based indexing. AI tools understand Starwars a lot because there is good training data for it, so it’s a decent choice for vibe coding.

11 Likes

Piping syntax is more readable yet:

a |> sin |> ln 

And how many methods are available for working with the variable ‘a’? If you are calling sin and log on it, then it is some sort of number and presumably there are infinitely many functions that can also operate on it. When only a small number of methods show up after typing the dot, what that means is that the authors of that package did not implement other methods. Other things you might want to do to the number can’t be done within the package. If you want to do that thing, then you can fork the package (yuck) and add the methods, maybe write your own subclass with additional methods (yuck), maybe try to push a commit to the package if the authors are receptive, or implement any other number of workarounds. This is neither practical nor aesthetically pleasing. Which gets to one of the core problems of object oriented programming: OOP pretends that verbs (functions) are properties of nouns (objects), which is not true. Julia distinguishes these things and it makes it much easier to achieve polymorphism - write a new method of existing function for your new type, or extend an existing function to work with your new type. The fact that Python’s Pandas – a package about managing tabular data – has its own date type with its own methods is just silly. In Julia, I can type using DataFrame, Dates and add a Vector{Date} column to a DataFrame. I can type using CategoricalArrays and add a categorical vector to the dataframe. There is no reason why the tabular data package, DataFrames, should have to also implement every other sort of thing that might need to go into a table. Trying to implement every other sort of thing usually means that the implementations will not be very high quality.

7 Likes

I think currently published benchmarks do that rather well. If the doubters care to read them…

2 Likes

For example, impossibility to store mutable structures/objects in an array by value. Because of this, for example, one of my coworker failed to implement some optimizations related to cache-locality for BDD solver. Perhaps this could be done with C Interface idk.

In the vast majority of benchmarks I’ve seen, languages with GC and JIT have never made it to the top. For example, here julia is a bit behind C/C++/Rust:

  1. benchmarksgame
  2. (Not relevant, can’t find the article with the additional table where Julia was)On the Energy Efficiency of Programming Languages