To preface, this is just one person’s attempt to help people save some time and engage with a new language with more reasonable expectations, not a standalone or authoritative source of information. I expect learning about Julia or any mentioned topics to be mostly accomplished by studying other sources, and I just hope that my language here is precise enough to provide some helpful search terms. If a reader only needs to skim the headers or read a few sections, that’s fine.
Misconception 1: Julia is the last programming language, the one that will replace all others
Reality: Julia offers an optimizable dynamic language and aims to work alongside others
To be fair, this myth didn’t come out of nowhere. The first official blogpost in 2012 described the creators as “greedy” for features found in various programming languages, and the 2012 paper proposed Julia as an alternative to “two-tiered architectures” in technical computing with “high-level logic in a dynamic language while the heavy lifting is done in C and Fortran”, which was soon called the “two-language problem”. However, the blogpost didn’t list every language and their features, which would be futile because many features and paradigms are incompatible. The 2012 paper also declared calling C and Fortran routines as a core feature, so despite aiming to reduce the need to wrap those languages, Julia was also designed to do so. Likewise, Julia users embrace great work done in other languages, so the ecosystem does not shy away from wrapping binaries and interoperability. To return the favor in the former way, the experimental JuliaC aims to produce binaries of a manageable size.
Like most programming languages, Julia is an opinion on how to program, specifically in an optimizable dynamic language, and it’s entirely valid to have different opinions and needs that are better served by other languages, for now or forever. Languages have varying similarities to Julia, though I don’t know of a language similar enough to avoid learning and adjusting; for example, Julia’s concept of variables and mutability is more like Python’s than C’s, but it has structs more like C’s than like Python’s classes. There are also many good reasons to use a language besides its base design. Your colleagues might communicate and collaborate in certain languages, and many great (and well-funded) developers maintain a particular library in one. People can have any number or kind of reasons to invest their time in any practical language, so the most general advice I can give is to consider your needs and keep an open mind.
Misconception 2: Julia’s compiler is an easy guarantee of peak performance
Reality: Optimizing compilers aren’t the only reasons for performance, and languages without one can be fast
The fact that some interactive languages lack optimizing compilers often misleads the users into believing that their code is suboptimal for that reason, and they are surprised when otherwise correct Julia ports don’t improve performance.
- “Slow” interactive languages can load binaries compiled from fast (and not-so-interactive) languages that occupy almost all of the runtime, a negligible difference from using the fast languages directly. Practical languages, including Julia, generally do this to avoid the maintenance burden of reinventing wheels. The limitation is that loaded compiled code can’t be further optimized together or with the wrapper language, but that’s not always needed for great performance, especially if the bottleneck is elsewhere.
- A compiler is not designed to change the code’s meaning, so performance primarily depends on the algorithm. If you’re observing great performance and optimizations in seemingly simple code, very good developers studied and implemented algorithms for you.
- Compilers don’t do all known optimizations. That’s why there are libraries with platform-dependent routines, even in assembly.
Misconception 3: I don’t need to think about types in a dynamically typed language
Reality: Julia has important type annotations and parameters
It’s technically possible to write Julia programs without type annotations and for them to have great performance, and there is an unfortunate number of language introductions that demonstrate this in comparison with other dynamically typed languages. A non-exhaustive list of explicit types that matter:
- Multiple dispatch requires annotating argument types to distinguish different methods.
structtypes typically have carefully annotated field types to help the compiler infer types of expressions and optimize code down the road. This benefit is accomplished by static types and parameters in other languages.- Type parameters change what code does.
Int[1,2,3]restricts the vector’s elements toIntobjects, and it is stored differently and more optimizable than the unrestrictedAny[1,2,3]. - Even type-generic code would benefit from type computation. Instead of initializing a sum with
result = 0, computing the correct type from the start likeresult = zero( eltype( input_vector))prevents type incompatibilities down the road and helps the compiler optimize.
The element of truth is that type inference really cuts down how much we explicitly write. This feature is also present in many statically typed languages, and despite the stereotype, the frequency of explicit types doesn’t actually distinguish static and dynamic typing.
Misconception 4: Multiple dispatch generalizes single dispatch in object-oriented programming, so the runtime overhead must slow down the program
Reality: Multiple dispatch usually occurs at compile-time and is not a generalization of OOP
I’m guessing that this is a mixture of overgeneralizations in the few relevant Wikipedia articles and some misinterpretation. Compilers for many languages can dispatch calls at compile-time over inferred dynamic types, and Julia’s Performance Tips are largely about achieving multiple dispatch at compile-time to enable compiler optimizations. It’s similar in principle to function overloading, which is multiple methods over static types. While multiple dispatch does generalize single dispatch and single dispatch is often represented by object-oriented languages, multiple dispatch in Julia (and CLOS) is not compatible with class encapsulation of methods, the basis of OOP and its particular perks.
Misconception 5: Compiler-inferred and annotated types are static types
Reality: Julia is considered to be strictly dynamically typed
Type inference and static dispatch during compilation has led a few people to assert that Julia has static or gradual typing, but that miscategorization is based on a divide between static and dynamic typing present in a handful of language implementations. The divide breaks down beyond that e.g. a GHC option to defer compiler-detected type errors in Haskell to runtime, and it’s worth acknowledging that there isn’t a universal and precise concept of dynamic vs static typing, let alone other programming terminology. The typical key points that justify Julia’s strict dynamic typing are:
- Method dispatch is entirely based on the types of runtime objects. There is no separate dispatch feature over the compiler-inferred types associated with expressions.
- Compiler-inferred types are not on the language level, and the perk is allowing type inference to improve across patches and minor versions. Optimizations do typically occur when they match the runtime types exactly, but optimizations can also occur when they don’t.
- Although a type can annotate a variable or field to restrict the types of assigned objects, the declared type is still a runtime object and is thus allowed to be unknown at compile-time e.g.
var2::typeof( (var1::Ref{Any})[] ) = valuecan only infervar1[]as the universal supertypeAny, but its runtime type restrictsvar2instead. It can also diverge from the compiler-inferred type e.g.x::Number = 1could inferxasInt.
Misconception 6: Multiple dispatch lets us use any libraries together
Reality: Composability is more flexible but requires effort to implement
Multiple dispatch and JIT compilation does facilitate independent packages working together or extending each other, e.g. Package1.function1( a::AbstractArray) working on an instance of Package2.Type2 <: AbstractArray, which falls under the systems principle of composability. However, that does not mean that any two packages are automatically composable. Package-mixing calls can fail upon missing methods or incompatible types, and although Julia can catch these during precompilation given enough static dispatch, Julia does not yet have a formal interface system to facilitate method implementations in isolation. These are the relatively convenient fraction of API issues. Algorithm bugs typically must be caught by runtime tests, and subtler wrong assumptions are sometimes only challenged by an independent package down the line. Composability and its principles also exist in other languages, often referred to by different terms like duck typing, and like any good feature, it needs effort, tests, reports, and patches.