Frequent Julia beginner myths

I don’t intend this to be a thorough list of myths, and I can’t assert any hard numbers about how often beginners believe them. However, these did appear repeatedly on forums and blogposts over the years and I believed some at first, so hopefully this can be useful to someone else. In a rough order of more frequent to less…

Julia does not aspire to be the last programming language

This distortion of Julia’s goals crops up often, sometimes to be “debunked.” To be fair, it 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 Julia was never intended to be the only language in use.

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. For example, Julia is fairly permissive of side effects, so referential transparency and its perks are far more feasible in pure (or more dedicated) functional programming languages. Julia could very well be the last programming language for an individual, but many other programming languages already were.

Optimizing compilers do not imply good performance is effortless or that languages without one are slower

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 their compiled Julia translations don’t improve performance. Base language design and compiler implementation are not the only sources of good 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 generally do this to avoid reinventing (and separately maintaining) wheels, so does Julia. The limitation is that loaded compiled code can’t be further optimized together or with the wrapper language.
  • A compiler is not designed to change the semantics of the code, so performance primarily depends on the algorithm. If you’re observing great performance and optimizations in seemingly simple code, someone implemented it for you.
  • Compilers don’t do all known optimizations. That’s why there are libraries with platform-dependent routines, even in assembly.

Multiple dispatch is not usually runtime/dynamic dispatch 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 runtime types, and Julia’s Performance Tips are largely about achieving static multiple dispatch 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.

Composability is easier to use, not effortless to implement

Multiple dispatch and JIT compilation 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.

Julia really is 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 across some language implementations. The divide breaks down beyond that e.g. a GHC option to defer compiler-detected type errors in Haskell to runtime. I don’t think there’s a universal agreement on the exact distinction, but some key points that justify Julia’s strict dynamic typing are:

  • Julia only dispatches methods on types of runtime values
  • Compiler-inferred types are not on the language level, and methods cannot be dispatched over them if they are different from runtime types. The perk is allowing type inference to improve across patches and minor versions.
  • Although types can be declared for variables and fields, they are still runtime types that serve to restrict the assigned runtime values’ types, and they are allowed to be compiler-uninferrable e.g. var2::typeof( (var1::Ref{Any})[] ) = value can only infer var1[] as ::Any, but its runtime type restricts var2 instead.

So while it’s possible to write Julia programs where the compiler infers the runtime types for all expressions, there is neither syntax nor method dispatch on the language level for distinct static types. No existing Julia implementation is limited to such a program subset, either; the experimental JuliaC package’s --trim option still aspires to support limited runtime dispatch.

13 Likes

Rang a bell, which is something Brian Kernighan wrote—rather than resorting to wishful thinking to improve performance by piling on “optimizations,” find a better algo.

1 Like