Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Julia is a language that looks very simple, but the more you use it, the more you realize how complex and unpredictable it is.

I think that Union types do more harm than good (why would you want a function to return Union of int and float instead of compile error? It totally slows down the program)

Array{Number} is totally different from Array{:<Number}, and shouldn’t be allowed, as it is inefficient.

1-based indexing was a mistake, and I have seen it emitting inefficient code in the PTX compiler.

But the worst and hardest part is the rule/heuristic for multiple dispatch: it’s so complex, that it isn’t even documented. It should probably throw more errors and be predictable instead of trying to be so smart.



> 1-based indexing was a mistake

The most software I write, the more I am convinced that 1-based indexing is what most languages should be using, either the exception being the very specific use-case of needing to track offsets by hand for some reason. 1-based indexing is so much easier to reason about, but zero based indexing is seen as “the one true way” due its ubiquity, and not because it’s actually better.

> and I have seen it emitting inefficient code in the PTX compiler.

Have you raised this with the devs? This seems like something worth raising as an issue.


Both approaches have their advantages, IMO. Inclusive ranges are usually easier to understand, but e.g. when picking a (uniformly distributed) random element from an array, nothing beats a[floor(rnd() * len(a)].


How is that very readable?

In Julia, this will pick a random value from an array: rand([3, 4, 5, 1])

While this picks a value from a range: rand(1:10)

Much more straightforward to read IMHO. Where I prefer 0-based index is when dealing with coordinate systems and memory locations.


> nothing beats a[floor(rnd() * len(a)]

What's wrong with rand(a)?


Already mentioned, in julia just do rand(a). But if you didn't have that, couldn't you just switch out floor for ceil and have the exact equivalent for 1 based indexing?


No - rnd() can not return one, but can return zero, at which point you'd access index 0 even with ceil().


Ah, thanks! I think I'll stick with rand(indexable_collection) in Julia since anything else just doesn't feel Julian, but I'll remember that for other 1-based languages if I come across it.


To tell you the truth, I don’t really care about 0 or 1 based indexing, there’s not much difference when I’m coding.

At the same time if you look at TIOBE, 0 based indexing won.

The biggest problem for me was porting numerical code from Ruby: the syntax is so similar to Julia, that I didn’t have much to do, but porting 0 based code to 1 based was the biggest work actually.

I’m sure it’s painful to port code from R or Marlab if it’s 0 based, but that will need to happen anyways.


Returning a Union of int or float isn't that useful but the point is that Julia is a dynamic language, and if there was no implicit union type it would have to just box the return type into an "Any" box, which actually slows down the program (the union here will cause functions to have two optimized versions, one for int and one for float instead of a generic dynamic one for Any). If it had a compile error for every function that can return more than one type it would make the language even more restrictive than some static languages.

Though for intentional uses of unions, most of the time I use a union of a success type or an error type, or a union of a type and null, or a union of type and missing. They could all be special cases, but I don't see the point of not being just one mechanism.


My C-like language has union types and it's amazing. Basically it makes it really simple to add an upgrade path to a new type in an interface - you can just switch from taking Class1 to taking Class1|Class2, and the compiler guarantees you are handling both cases everywhere it comes up. Then you can gradually switch all your calling code to the new type, and then remove the old type at your leisure.

In a language without union types, you'd just have two fields for both cases, and you'd have to check everywhere that you're handling both cases and that you can't get into broken situations where both or neither fields are set. Union types give you peace of mind there.


That's what sum types are for.


That's what "union types" are, sum types.


For many people (myself included), there is a distinction: A sum type is a tagged union. Using ‘|’ for union types and ‘+’ for sum types, the difference is that for a type T the type T | T is simply T again, whereas T + T is two copies of T. That is, given an element x of T + T, I can tell whether x comes from the first or the second copy of T. This means, for example, that T + null always adds a new null element to your type, whereas T | null only adds a null element if none existed before.

If your type system has a different way of turning one type into two different (but isomorphic) types (for example by wrapping them in records with a single field), you can simulate sum types with union types. Hence union types are more general than sum types.

However, in my experience you (almost?) always want sum types anyways. In this case union types being the basic construction tends to be a disadvantage because the basic construct it usually more convenient and therefore gets used. Union types tend to work fine for a while and once you realize that sum types would be better, you have quite a bit of refactoring to do.


There are also advantages to union types. For example, a Union{T1, T2} can be narrowed to just T1 (by the compiler or the user) without being a breaking change - indeed, the compiler is free to do exactly that in Julia.

In contrast, in Rust, the compiler can't look at a function that produces an Option<T>, decide that in this case, it will always return a Some(T), and then have it return a T instead. That happens all the time in Julia.

And also, sum types take more boilerplate code to use than union types.

In my opinion, sum types are more useful for static analysis, union types are more useful for ease-of-use.


I was not aware of this distinction.


I think GP was telling they are overused and harm performance.


I actually have very similar experiences. I want to like Julia and I look at it from time to time. The hype is always that it allows one to write C-speed code with the simplicity of python, without the idiosyncrasies of numpy. But every time I look I find just as many little things to look out for if one cares about performance, don't use abstract types is another one.

I understand why this is the case and I don't have an issue generally with it, but considering that I know Python well, and know how to speed up Python using numba, pythran or cython, I don't see what I would gain by investing in Julia.

Instead I've now decided to learn Rust, as it fills a different niche and I can use it for writing python modules for some of my bottleneck code.


This chimes so much with me. I started coding in ~2011. I know how to use Cython, Numba, etc. and the options for accelerating Python are getting better all the time. I'm a confident C/C++/Fortran programmer by virtue of having worked in those languages for HPC codes anyway. Whenever I've tried to use Julia for things, it just feels like an awkward hybrid of Python and C++.

Plus, I think people forget that Python now works pretty well on Windows, and I don't think the same can be necessarily said for Julia. In my University, we support a lot of people on Windows with Python packages - particularly outside of traditonally HPC type areas.

As a final thing - on one occasion last year, I tried to integrate a Julia interpreter into an application - something Python does really well on all platforms. I found that copying the code from the website example verbatim did not work on Windows, didn't appear to have done for the best part of a year, and wasn't in the testing framework. I've just checked and it's still not resolved.


Examples being out of date is definitely an issue since a lot of things are still changing quickly in the ecosystem as a whole. I think it’s getting better as more packages go 1.0 and internals stabilize a bit, but nonetheless.

As far as your comment about “awkwardness”, the key point for me was when I actually started to understand/embrace multiple dispatch as a programming paradigm. If you try to write Julia like in a an imperative or oo or etc. style, it may work, but will be clunky and quite possibly full of type instabilities. But if you write Julia in a “dispatch-oriented” paradigm then it really is just as performant and elegant as advertised, IMO.


I think one based makea sense if you consider it is targeting users who want their matlab to run faster but not look like c. Or people who want their fortran "codes" to look more like matlab but not run any slower. I always considered it a language for physicists and meteorologists. That isn't going to top the tiobe even if that actually was a good metric.


Yeah, this is similar to my experience. I used to like the language a lot but there are many aspects of it that are a hindrance to productivity.

I think Swift with the best bits of Julia would be the perfect general purpose language.

Some people think multiple dispatch is a killer feature but in langauges like Swift, extensions are a much neater approach.


My biggest issue is the lack of coherence in the ecosystem, from using macros in whatever cases, to the inability to do otherwise trivial stuff in python, or the huge bottleneck of unpacking (star operator in python). I tried to use it for a class and there was just too much friction between the expected behavior and the observed behavior that I gave up.

My issue with python is speed, but I will probably end up implementing what I want/need in rust and call that from python.


> to the inability to do otherwise trivial stuff in python

Like what?

> or the huge bottleneck of unpacking (star operator in python)

In what sense is unpacking a bottleneck?


Unpacking (or splatting) can be excruciatingly slow in Julia if you have more than a few dozen elements in the iterable.

That happens because it gets splatted into a tuple with N elements, which in Julia has a type parameter for every element - i.e. a generic type Tuple{N1, N2, N3 ... N} with as many type parameters as there are elements in the iterable. And the compiler has a hard time working with an instance of a type with e.g. 10,000 parameters.


Was this improved in recent versions? I tried the following in Julia 1.6-rc1 and they run instantly:

    @time v = [(1:10_000)...];  -> 0.000639 seconds

    f(args...) = sum(args)
    @time f(v...);              -> 0.001039 seconds


That seems like a niche case. Surely flattening arrays is faster.


> In what sense is unpacking a bottleneck?

Performance wise, I can't recall the exact scenario though.


> Array{Number} is totally different from Array{:<Number}, and shouldn’t be allowed, as it is inefficient.

Rest of this I disagree with, but this one I just don't understand. Why is it wrong that it's totally different?

Edit: missed a word which changed the meaning of my question


These are two different types in Julia. The first is an array of numbers. The second is an infinite set of types, namely "all arrays which have Number or a subtype of Number as its element type".

There is a distinction, because e.g. `Array{Int32}` is a subtype of the latter, but not the former (because the former is a concrete types that does not have subtypes).


But that seems very fine and specific to me. I don't understand why it shouldn't be allowed to be like this.


Array{Number} is an array of objects with unknown type and size, basically an array of pointers with possibly totally different method implementations.

Array{:<Number} is when LLVM gets the exact type at compile time and can optimize the code using SSE instructions.

You have a factor of 10 between execution, and if you are not experienced, you don't understand why your code is slow and why the garbage collector kicks in so hard.




Consider applying for YC's Fall 2026 batch! Applications are open till July 27.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: