Introduction#

Learning Objectives#

  • Understand the core design principles of Julia

  • Recognise the main features that make Julia suitable for numerical and scientific computing

  • Explain the advantages of multiple dispatch in Julia

  • Appreciate the high-performance capabilities of Julia due to its Just-in-time (JIT) compilation

  • Know how to start and use the Julia REPL

Julia’s Background and Goals#

Julia’s core goal is to address the “two-language problem” often encountered in scientific computing. It was designed to combine the ease of use of high-level language (like Python) with the speed of low-level languages (like C or Fortran). Julia aims to be a single language that is easy to write and fast to run. The creators of Julia envisioned a language that had:

  • the dynamism and productivity of Python

  • the mathematical ability and convenient syntax of MATLAB

  • and the raw performance of C/Fortran

all in one language. Julia achieves this by being a just-in-time compiled language using the LLVM framework, meaning it compiles functions to efficient machine codes on the fly for high performance.

Core Design Principles#

A key design principle of Julia is multiple dispatch, a programming paradigm that makes it easy to express many object-oriented and functional patterns. Multiple dispatch means that the method (implementation) of a function to execute is chosen based on the types of all of its arguments. This allows developers to define generic function names that have specialised methods for different type combinations.

For example, you could define an add function that has different methods for adding integers, adding floating-point numbers, or even concatenating strings, and Julia will pick the appropriate method based on the argument types at runtime. This leads to code that is both flexible and optimised for performance for each type of scenario.

For example, the code below would create two functions for performing addition, one that takes in Integers (::Int) and the other Strings (::String).

# Define `add` for Ints (numeric addition) and for Strings (string concatenation)

function add(x::Int, y::Int)
    return x + y         # integer addition
end

function add(x::String, y::String)
    return x * y         # string concatenation
end

When the function add is called, Julia automatically selects the most specific method based on the argument types:

add(5, 7)               # Calls the Int method: returns 12
add("foo", "bar")       # Calls the String method: returns "foobar"

Each method shares the same name add but has different implementations for the types it handles. This makes the code:

  • Clear and concise: There is no need to add different code paths through functions based on checking types of arguments.

  • Fast: Julia compiles a specialised version for each method call at runtime.

  • Extensible: New implementations of add can easily be added for new combinations of types.

Julia is also designed to be dynamically typed; you do not need to declare types for variables, as the type is determined at runtime, alongside using automatic memory management (garbage collection), so you don’t have to manually allocate or free memory in typical usage.

Main Features and Strengths#

Julia incorporates many features that make it powerful for numerical and scientific computing:

  • High Performance: Thanks to JIT compilation using LLVM, Julia can approach or match the speed of C and Fortran for many tasks, meaning you often don’t need to rewrite performance-critical code in another language. Code written in pure Julia is typically fast if written with performance in mind.

  • Multiple Dispatch: Multiple dispatch allows the standard library and packages to define methods optimised for different types (e.g. arrays of floats vs ints) seamlessly. This contributes to performance. It also makes it straightforward to extend the code to support new types as-and-when useful.

  • Good support for Numerical Computing: Julia has built-in support for things like arbitrary precision arithmetic, regular expressions, and powerful libraries for linear algebra and random numbers. Julia’s package manager makes it easy to add external packages for specialised functionality, such as statistics, plotting, optimisation, etc.

  • Interactive and Reproducible: Julia can be used interactively (e.g. via the REPL or Jupyter notebooks) just like Python or R, which is great for exploration. Julia also has the built-in Pkg standard library that uses Project and Manifest files to pin exact dependency versions and ensure your environment can be recreated anywhere.

  • Learning from older languages: To some extent, Julia has been able to implement lessons that other languages had to learn the hard way, especially around it’s tooling. For example, its package management system is easy to use, it has unit testing support directly in the standard library, and it has a a go-to documentation generator.

It should be noted that Julia comes with some weaknesses too:

  • Because Julia is a relatively young language, it has an immature ecosystem. Julia doesn’t have the same financial backing or support from large companies as a language like Python, so most packages and tools for Julia are being maintained for free by volunteers. It’s therefore not uncommon to come across packages with patchy documentation or that are not being maintained.

  • The core language suffers from some bugs and incorrect documentation in places.

  • Compile time latency: because of the just-in-time compilation that Julia performs, a user can experience a noticeable lag before any ‘actual’ code gets run, especially when importing heavy-duty packages. This can make it frustrating to use if you just want to quickly run a script.

  • Large memory consumption at runtime: the Julia runtime is large, to the extent where just running a ‘Hello world’ script can consume about 200MB of memory. (For comparison, Python consumes around 9MB.) While this isn’t a problem in most research settings, it does mean Julia isn’t a viable option where memory is severely restricted e.g. embedded programming.

Overall, Julia was designed to increase user productivity without sacrificing performance. You can write high-level code that looks very similar to pseudocode or Python. Julia will compile efficient machine code, making it suitable for research and data science, where you want to prototype quickly and then run computations efficiently on extensive data or simulations.

Julia REPL Basics#

The Julia REPL (Read-Eval-Print Loop) is an interactive command‐line environment where you can type Julia expressions and see results immediately. To start the REPL, open your terminal and run:

julia

You should then see a prompt like:

julia>

Some useful REPL features include:

  • Evaluate code by typing an expression and pressing Enter.

  • Help mode: Type ? then a function name (e.g. ?println) to see documentation.

  • Package mode: Type ] to enter Pkg mode (pkg>), where you can add or update packages (e.g. add Plots) – more about this later.

  • Autocomplete: Press Tab after typing part of a variable or function name to see suggestions.

  • Exit by typing exit() or pressing Ctrl+D.

Exercise: Using Julia’s REPL#

  • Launch the REPL by running julia in your terminal.

  • Type each of these lines and verify you see the expected output:

julia> 1 + 1
2

julia> sqrt(2)
1.4142135623730951

julia> sum(1:10)
55

julia> function greet(name)
           println("Hello, $name!")
       end

julia> greet("Julia")
Hello, Julia!
  • Exit the REPL with exit() or Ctrl+D

Suppressing output#

You will notice that every time you enter an expression into the REPL then a value is printed back. This is also true when using Jupyter notebooks. Occasionally throughout this course we’ll use a semicolon ; at the end of a line to suppress the output, like so:

# No output printed due to ;
1 + 1;

This is mostly to avoid clutter in the Jupyter notebook cell outputs. More generally, we can use semicolons to evaluate multiple expressions on a single line:

# Multiple expressions on the same line
x = 1; x + 2
3

The @show macro#

Another feature we will occasionally use is the @show ‘macro’. This prints an expression and its result, which is convenient for printing the values of multiple expressions in the REPL / Jupyter code cells. (By default, only the value of the last expression is printed back when using a REPL / notebook code cell.) For example:

# Printing each expression and value
@show 1 + 1
@show sum(1:10)
1 + 1 = 2
sum(1:10) = 55
55

The @show macro can also be quite useful when debugging code, to print the values of key expressions while the code is running.

End of Section Quiz#

What was the primary motivation for developing the Julia programming language?

What is multiple dispatch in Julia?