Description: https://images.manning.com/360/480/resize/book/f/7cb3b51-6ce7-4f7e-95d3-5b86bc1d980a/Engheim-MEAP-HI.png

An excerpt from Julia as a Second Language by Erik Engheim

This article covers:

  • What type of problems Julia solves.
  • The limits of statically-typed languages.
  • Why the world needs a fast dynamically-typed language.
  • How Julia increases programmer productivity.

Read it if you’re interested in the Julia language and its strengths and weaknesses.


Take 25% off Julia as a Second Language by entering fccengheim into the discount code box at checkout at manning.com.


You can choose from hundreds of programming languages, many of them far better known than Julia. So why pick Julia? The short answer is that Julia has broad applications, many capabilities, and is easy to learn and use. It’s excellent for data science, complex linear algebra, data mining, and machine learning, but you can use it for so much more.

What is Julia?

Julia is a general-purpose, multi-platform programming language that:

  • Is numerical
  • Is dynamically-typed
  • Has automatic memory management (garbage collection)
  • Is high-performance and Just-in-Time compiled (JIT)

Okay, that’s a lot, and some of these things sound like contradictions. How can Julia be a general-purpose language and also tailored towards numerical programming? It’s general purpose because, like Python, Julia can be used for almost anything. It’s numerical because, like Matlab, Julia is well suited to numerical programming.

Pros and cons of statically-typed languages and dynamically-types languages

Let’s focus on one aspect of Julia: the fact that it’s dynamically-typed. Usually programming languages are divided into two broad categories:

  • Dynamically-typed
  • Statically-typed

In static languages, expressions have types; in dynamic languages, values have types.

— Stefan Karpinski, creator of Julia

Examples of statically typed languages are C/C++, C#, Java, Swift, Go, Rust, Pascal, and Fortran. In a statically typed language, type checks are performed on all your code before your program is allowed to run.

Examples of dynamically typed languages are Python, Perl, Ruby, JavaScript, Matlab, and LISP. Dynamically typed languages perform type checks while the program is running. Unfortunately, dynamically typed languages tend to be very slow.

In dynamic languages, values such as numbers, characters, and strings have attached tags which say what type they are. These tags make it possible for programs written in a dynamically-typed language to check type correctness at runtime.

Julia is unusual in that it is both a dynamically-typed and high performance language. To many this is a contradiction. This unique trait of Julia is made possible because the language was specifically designed for just-in-time (JIT) compilation and uses a feature called multiple-dispatch for all function calls. Languages such as C/C++ and Fortran use ahead-of-time (AOT) compilation. With AOT compilation, a compiler translates the whole program into machine code before it can run. Other languages, such as Python, Ruby, and Basic, use an interpreter. With interpreted languages, a program reads each line of source code and interprets it at runtime to carry out the instructions.

Now that you have some idea of what kind of language Julia is, we can begin talking about the appeal of Julia.

Julia combines elegance, productivity, and performance

While performance is one of the key selling points of Julia, what caught my attention back in 2013 when I first discovered Julia was how well-thought-out, powerful, and easy to use it was. I had a program I had rewritten in several languages to compare how expressive, easy to use, and productive each was. With Julia, I managed to make the most elegant, compact, and easily-readable variant of this code. Since then, I have tried many programming languages, but have never gotten close to what I achieved with Julia. Here are couple of one-liners giving some sense of the expressiveness of Julia.

Listing 1. Julia one-liners

 
 filter(!isempty, readlines(filename)) # strip out empty lines
 filter(endswith(".png"), readdir())   # get PNG files
 findall(==(4), [4, 8, 4, 2, 5, 1])    # find every index of the number 4
  

Having been programming since the 1990s, I have had periods where I had had enough of programming. Julia helped me to regain the joy of programming. Part of the reason is that once you master Julia, you will feel that you have a language that works for you rather than against you. I think many of us have had the experience of working on a problem where we have a good idea of how to solve it, but the language we are using is getting in our way. The limitations of the language force us to cobble together inelegant solutions. With Julia, I can build software the way I want without the language putting up obstacles.

Another aspect that adds to your productivity and sense of fun is that Julia comes bundled with a rich standard library. You hit the ground running. You can get a lot done without hunting all over the web for some library to do what you want. Julia has you covered whether you want to do linear algebra, statistics, HTTP, string manipulation, or working with different date formats. And if the capability you want isn’t in the standard library, Julia has a tightly integrated package manager that makes adding third-party libraries a breeze.

Programming Julia almost makes you feel guilty or spoiled, because you can build rich and elegant abstractions without taking a performance hit.

Another important advantage of Julia is that it is easy to learn. This ease of learning has helped Julia gain a larger community over time. To understand why Julia is easy to learn, consider the ubiquitous “hello world” program written in Julia:

 
 print("Hello world")
  

When run, this code writes the text “hello world,” to the screen. While trivial, many languages require a lot of complex scaffolding to do something that simple. This is a Java program which does the same thing:

 
 public class Main {
     public static void main(String[] args) {
         System.out.print("hello world");
     }
 }
  

That exposes the beginner to a lot more concepts all at once, which can be overwhelming. Julia is easier to learn because we can focus on learning one concept at a time. You can learn to write a function without ever seeing a type definition. With a lot of functionality available out of the box, you don’t even need to know how to import external libraries to write helpful code.

Why Julia was created

To truly understand what Julia brings to the table, we need to understand better why Julia was created in the first place. The creators of the Julia programming language wanted to solve the so-called the two-language problem.

It refers to the fact that a lot of software is written using two different programming languages, each with different characteristics. In the scientific domain, machine learning and data analysis dynamic languages are often preferred. However, these languages usually don’t give good performance. Thus solutions often have to be rewritten in more performant, statically-typed languages. But why does this preference exist? Why not write everything in a traditional high-performance statically-typed language?

Scientists need the interactive programming that dynamically-typed languages offer

Scientists began writing software such as large weather simulations in Fortran[1] and neural networks[2] in C or C++[3]. These languages offer the kind of performance you need to tackle large problems. However, the performance comes at a price. These languages tend to be rigid and verbose, and lack expressiveness, all of which reduce programmer productivity.

The fundamental problem, however, is that these languages are not suited for interactive programming. What do I mean by that? Interactive programming means the ability to write code and get immediate feedback.

Interactive programming matters a lot in data science and machine learning. In a typical data analysis process, data is explored by a developer loading large amounts of data into an interactive programming environment. Then the developer performs various analysis of this data. It could be finding averages and maximum values. It could be plotting a histogram. The results of the first analysis tells the programmer what the next steps should be.

Figure 1 shows this process in a dynamically-typed language. You start by running the code, which loads the data, which you can then observe. However, you don’t have to go through this whole process after you change the code. You can change code and observe changes immediately. You don’t need to load massive amounts of data again.


Figure 1. In dynamically-typed languages we can ping-pong between coding and observing. Large data sets do not need to be reloaded into memory.


Let’s contrast this experience with the use of a statically-typed language such as Fortran, C/C++ or Java[4]. The developer would write some code to load the data and explore it, without knowing anything about what the data looks like. They would then have to wait for the program to:

  1. Compile
  2. Launch
  3. Load a large amount of data

At this point the developer sees a plot of the data and statistics, which gives them the information they need to choose the next analysis. But choosing the next analysis would require repeating the whole cycle over again. The large blob of data has to be reloaded on every iteration. This makes each iteration very slow. This slows down the whole analytical process. This is a static, non-interactive way of programming.


Figure 2. Statically-typed languages requires the whole loop to be repeated.


Developers in other fields also need the interactivity that a dynamically-typed language offers

This problem isn’t unique to scientists. Game developers have long faced the same problem. What we call a game engine is usually written in a language such as C or C++ that can compile to fast machine code. This part of the software often does well-understood and well-defined things such as drawing objects on the screen and checking if they collide with each other.

Like a data analyst, a game developer has a lot of code which will need numerous iterations to work satisfactory. Specifically developing good game play requires a lot of experimentation and iteration. One has to tweak and alter code for how characters in the game behave. The layout of a map or level has to be experimented with repeatedly to get it right. For this reason, almost all game engines use a second language which allows for changes to code on the fly. Frequently these are in languages such as Lua[5], JavaScript, or Python[6].

With these languages, the code for game characters and maps can be changed without requiring a recompile and reloading of maps, levels, and characters. Thus one can experiment with game play, pause, make code changes, and continue straight away with the new changes.

Machine learning professionals face similar challenges. They build predictive models, such as neural networks, which they feed large amounts of data to train. This is often as much of a science as an art. Getting it right requires experimentation. If you need to reload training data every time you modify your model, it will slow down the development process. For this reason dynamically-typed languages such as Python, R, and MATLAB became very popular in the scientific community.

However, because these languages aren’t very fast, they get paired with languages such as Fortran or C/C++ to get good performance. A neural network such as TensorFlow[7] or PyTorch[8] is made up of components written in C/C++. Python is used to arrange and connect these components. Thus, at runtime we can rearrange these components using Python without reloading the whole program.

Climate models and macroeconomic models may get developed in a dynamic language first and tested on a small dataset while being developed. Once the model is finished, many organizations will hire C/C++ or Fortran developers to rewrite the solution in a high-performance language. Thus we get an extra step, complicating the development processes and adding costs.

Julia’s higher performance solves the two-language problem

To solve the problem of having to use two languages, Julia was created. It makes it possible to combine the flexibility of a dynamically-typed language with the performance of a statically-typed language. That is why we like to say that:

Julia walks like Python. Runs like C.

— Popular saying in the Julia community

Using Julia, developers in many fields can write code with the same productivity as with languages such as Python, Ruby, R, and Matlab. Because of this, Julia has had profound impact on the industry. In the July 2019 edition of Nature, there are interviews with various scientists about their use of Julia.

For instance, at the University of Melbourne, they have seen an 800x improvement by porting computational models from R to Julia.

You can do things in an hour that would otherwise take weeks or months.

— Michael Stumpf

Systems Biologist UOM

Jane Herriman, Materials Science Caltech, reported seeing tenfold-faster runs since rewriting her Python code in Julia.

At the International Conference for Supercomputing in 2019 (SC19), Alan Edelman, one of the Julia creators, recounts how a group at the Massachusetts Institute of Technology (MIT) rewrote part of their Fortran climate model in Julia. They determined ahead of time that they would tolerate a 3x slowdown of their code. That was an acceptable tradeoff to get access to a high-level language with higher productivity in their view. Instead, they got a 3x speed boost by switching to Julia.

These are just a few of the many stories that abound today about how Julia is revolutionizing scientific computing and high-performance computing in general. By avoiding the two language problem, scientists can work much faster than before.

Julia is for everyone

These stories might give the false impression that Julia is only for brainiacs in white lab coats. But nothing could be further away from the truth. It turns out that a lot of the traits that make Julia great language for scientists also makes is a great language for everybody else. Julia offers:

  • Strong facilities for modularizing and reusing code.
  • A strict type system which helps catch bugs in your code when it runs.
  • A sophisticated system for reducing repetitive boilerplate code (metaprogramming[9]).
  • A rich and flexible type system which allows you to model a wide variety of problems.
  • A well-equipped standard library and variety of third party libraries to handle a variety of tasks.
  • Great string processing facilities. This is usually a key selling point for any Swiss-army knife style of programming language. It is what made languages such as Perl, Python, and Ruby popular initially.
  • Easily interface with a variety of other programming languages and tools.

While Julia’s big selling point is that it fixes the two-language problem, that does not mean the need to interface with existing Fortran, C or C++ code has been totally alleviated. The point of fixing the two-language problem is to avoid having to write Fortran or C code each time you hit a performance problem. You can stick with Julia the whole way. However, if somebody has already solved a problem that you have in another language, it may not make sense for you to rewrite that solution from scratch in Julia. Python, R, C, C++, and Fortran have large packages that have been built over many years. The Julia community can’t replace those overnight. To be productive, Julia developers need to be able to take advantage of existing software solutions.

In the long term, there is an obvious advantage in transitioning legacy software to Julia. Maintaining old Fortran libraries can require a lot more developer effort than maintaining a Julia library.

The greatest benefit is probably in the combinatorial power Julia gives. There are certain types of problems which requires the construction of large monolithic libraries. Julia, in contrast, is exceptionally well suited towards making small libraries that can easily be combined to match the functionality offered by large monolithic libraries in other languages. Let me give one example.

Machine learning powers self-driving cars, face recognition, voice recognition, and many other innovative technologies. The most famous packages for machine learning are PyTorch and TensorFlow. These packages are enormous monoliths maintained by large teams. There is no code sharing between them. Julia has a multitude of machine learning libraries such as Knet, Flux[10], and Mocha[11]. These libraries are tiny in comparison. Why? Because the capabilities of PyTorch and TensorFlow can be matched by combining multiple small libraries in Julia. Explaining more about why this works is a complex topic that requires a much deeper knowledge of Julia and how neural network libraries work. If you’re interested, I talk more about it in the book!

Many small libraries is an advantage with general application. Anyone building any kind of software will benefit from the ability to reuse existing pieces of software in a multitude of new ways instead of having to reinvent the wheel. With legacy programming languages, one often has to repeatedly implement the same functionality. TensorFlow and PyTorch, for instance, have a lot of duplicate functionality. Julia avoids duplication by putting a lot more functionality in libraries shared between many machine learning libraries. As you learn more about Julia, it will become increasingly clear to you how it can pull this off and why this capability is hard to achieve in many other languages.

What can I build with Julia?

In principle, you can use Julia can be used to build anything. However, every language has an ecosystem of packages and a community that may push you more towards other types of development than others. Julia is no different.

Julia in the sciences

Julia has a strong presence in the sciences. It is used, for example, in:

  • Computational biology
  • Statistics
  • Machine learning
  • Image processing
  • Computational calculus
  • Physics

Julia is used in other fields as well. For instance, it’s used in energy trading. The American Federal Reserve uses it to build complex macroeconomic models. Nobel Laureate Thomas J. Sargent founded QuantEcon, a platform that advances pedagogy in quantitative economics using both Julia and Python. He is a strong proponent of Julia, seeing as how the big problems in macroeconomics are difficult to solve with other programming languages.

In interviews with Lukas Biewald, Peter Norvig, a famous artificial intelligence (AI) researcher working at Google, has expressed how he thinks that the machine learning world would benefit greatly from switching to Julia.

I would be happier if Julia were the main language for AI.

— Peter Norvig

Author of Artificial Intelligence: A Modern Approach

Life sciences is another obvious area for Julia. By 2025, 2-40 exabytes of human genome data will be collected every year. Most mainstream software cannot handle data at that scale. You need a high performance language such as Julia that can work with a variety of formats on a variety of hardware with the highest possible performance.

Julia is found in contemporary medical science, too. The Julia package Pathogen is used to model infectious disease and is being widely used by COVID19 researchers.

Non-scientific uses of Julia

What about the non-science stuff? Julia also has a multitude of packages for other interests:

  • Genie – A full-stack MVC web framework.
  • Blink – Lets you make Electron GUI apps in Julia.
  • Gtk – For making a Julia GUI application using the popular Linux GUI toolkit Gtk.
  • QML – Lets you create cross-platform GUIs using the QML markup language, used in the Qt GUI toolkit.
  • GameZero – Game development for beginners.
  • Luxor – Drawing vector images.
  • Miletus – Writing financial contracts.
  • TerminalMenus – Allows interactive menus in the terminal.
  • Gumbo – Parsing HTML pages.
  • Cascadia – A CSS selector library. One can use this for web scraping, extracting useful information from web pages.
  • QRCode – Creating images of QR code popular with adds to show machine readable URLs.

As you can see, Julia has packages for general purpose programming.

Where Julia is less ideal

In principle, Julia can be used for almost anything, but being a young language means the selection of libraries is not equally comprehensive in every area. For example, the selection of packages for web development is limited. Building something like a mobile application would be difficult with Julia. It is also not great for small, short-running scripts, the kind you often write in Bash, Python, or Ruby. The reason for this is that Julia is JIT compiled.

That means Julia programs start more slowly than, for example, Python or Bash programs, but begin to run much faster once the JIT compiler has converted critical parts of the code to machine code. There is an ongoing effort in the Julia community to reduce this problem. There are a myriad of ways of tackling it, from better caching of previous JIT compilation to being more selective about when something is JIT compiled.

Julia is also not ideal for real-time systems. In a real-time system, software has to respond to things which happen in the real world. You can contrast this with, for instance, a weather simulator. With a weather simulator, it doesn’t matter what happens in the world outside the computer running the simulation.

However, if your program has to process data arriving from a measuring instrument every millisecond, then you can’t have sudden hiccups or delays. Otherwise you risk losing important measurements. Julia is a garbage collected language. That means data no longer used in your program gets automatically recycled for other purposes. The process of determining what memory to recycle tends to introduce small random delays and hiccups in program execution.

This problem should not be overstated. Robotics that require real-time behavior is being done in Julia. Researchers at MIT have simulated real-time control of the Boston Dynamics Atlas humanoid robot balancing on flat ground. This was to prove that Julia could be used for online control of robots by adjusting how Julia allocates and releases memory.

Julia is not well suited for embedded systems with limited memory. The reason is that Julia achieves high performance by creating highly specialized versions of the same code. Hence memory usage for the code itself would be higher in Julia than for, say, C/C++ or Python.

Finally, just like Python, Ruby, and other dynamic languages, Julia is not suited for typical systems programming such as making database systems or operating system kernels. These tasks tend to require detailed control of resource usage, which Julia does not offer. Julia is a high-level language aimed at ease of use, which means many details about resource usage have been abstracted away.

Hopefully, I’ve whetted your appetite and you want to learn more about Julia!

 


[1] Fortran is an old language for scientific computing

[2] Neural networks are a kind of algorithm inspired by the workings of the human brain.

[3] C and C++ are two related and widely used statically typed languages for systems programming

[4] Java is used for a lot of web server software and on Android phones

[5] Lua was originally made as a configuration language but is today primarily used to write computer games

[6] Python is one of the most popular langauges for data science and machine learning today

[7] TensorFlow is a well known neural network library for Python.

[8] PyTorch is a well known neural network library for Python.

[9] Metaprogramming is code which writes code. An advanced concept not covered in this book.

[10] Learn more about the Flux machine learning library here: https://fluxml.ai

[11] Mocha is a Julia Machine learning library created by MIT: https://developer.nvidia.com/blog/mocha-jl-deep-learning-julia/