The Julia Programming Language

The Bloqade project is built in the Julia programming language. For those who are not familiar with Julia, here is a quick start for some basic Julia grammar, and a guide for learning more about Julia and advanced usage.

Why Julia?

  • Fast. As you might have heard, Julia is very fast; there are various benchmarks online. It can be used to even write Basic Linear Algebra Subroutine (BLAS) to reach performance that is on par with the manually optimized assembly with C (check Octavian).
  • Generic. The language itself and its ecosystem are built to be generic, and the compiler can specialize on generic methods automatically. Thus, you will find that a lot things can be combined easily, and they will just work, e.g. plugging in the Measurement number from Measurement.jl into your ODE solver, you will get error propagation automatically; plugging in Tropical numbers or in general a semi-ring algebra into a tensor-network contraction function, you can solve optimization problems with tensor networks, and so on.
  • Differentiable. The language is differentiable, which means you can calculate the derivatives using an automatic differentiation (AD) engine on the whole language. The AD ecosystem in Julia is very well developed and supported. The current stable AD engine is powered by Zygote and the next generation AD engine includes Diffractor (check the video talk on ACM SIGPLAN) and Enzyme.
  • Extensible. The language is designed to be compiler friendly. It supports staged programming as well as compiler plugins. This makes supporting new hardware much easier. As a result, Julia can conveniently support multiple different hardware, such as CUDA, oneAPI, TPU, and potentially quantum computers in the future.
  • Easy. With all these powerful features, the language itself is still rather easy to learn. Let's go to the quick start section to skim through the basic syntax.

Multi-stage programming (MSP) is a variety of metaprogramming in which compilation is divided into a series of intermediate phases, allowing type-safe run-time code generation. Statically defined types are used to verify that dynamically constructed types are valid and do not violate the type system.

– Wikipedia

Quick Start

Variables and Some Basic Types

In Julia, you can define a variable similar to how you define it in Python. For example, you can define a x using = (assignment):

julia> x = 11

Every variable has a type. You can check it using typeof:

julia> typeof(x)Int64

By default, Julia displays the output of the last operation. You can suppress the output by adding ; (a semicolon) at the end.


In Julia, you can also define short-form, one-line functions using = (assignment) similar to how you write things mathematically.

julia> f(x) = 2xf (generic function with 1 method)

Typing the function's name gives information about the function. To call it, we must use parentheses:

julia> ff (generic function with 1 method)
julia> f(2)4

For longer functions, we use the following syntax with the function keyword and end:

julia> function g(x, y)
       	z = x + y
       	return z^2
       endg (generic function with 1 method)

Control Flows

In Julia, there are for, if and while control flows. For example, the for loop looks like:

julia> s = 00
julia> for i in 1:10 s += 1 end

we can now check the value of s by typing it again:

julia> s10

Here, 1:10 is a range representing the numbers from 1 to 10:

julia> typeof(1:10)UnitRange{Int64}

the if else statement looks like the following:

julia> if s < 10
       	# do something
       elseif 10 < s < 13
       	# do something
       	# do something

Matrix and Array

Julia carries its own Array type. If you use Python, it is similar to numpy.array in Python except:

  1. index starts from 1,
  2. the multi-dimensional index is column-wise.

You can also use list comprehension:

julia> [i for i in 1:10]10-element Vector{Int64}:

It works for multi-dimensional case too:

julia> [(i, j) for i in 1:10, j in 1:5]10×5 Matrix{Tuple{Int64, Int64}}:
 (1, 1)   (1, 2)   (1, 3)   (1, 4)   (1, 5)
 (2, 1)   (2, 2)   (2, 3)   (2, 4)   (2, 5)
 (3, 1)   (3, 2)   (3, 3)   (3, 4)   (3, 5)
 (4, 1)   (4, 2)   (4, 3)   (4, 4)   (4, 5)
 (5, 1)   (5, 2)   (5, 3)   (5, 4)   (5, 5)
 (6, 1)   (6, 2)   (6, 3)   (6, 4)   (6, 5)
 (7, 1)   (7, 2)   (7, 3)   (7, 4)   (7, 5)
 (8, 1)   (8, 2)   (8, 3)   (8, 4)   (8, 5)
 (9, 1)   (9, 2)   (9, 3)   (9, 4)   (9, 5)
 (10, 1)  (10, 2)  (10, 3)  (10, 4)  (10, 5)

Most functions involving matrices and arrays follow the same convention as numpy or MATLAB. For example, you can create a random matrix using:

julia> rand(5, 5)5×5 Matrix{Float64}:
 0.409915  0.455044  0.570374   0.478027  0.116082
 0.261576  0.897106  0.0692045  0.439551  0.552082
 0.863034  0.845997  0.174039   0.189362  0.216871
 0.553279  0.297514  0.0612171  0.702964  0.741098
 0.638206  0.971275  0.899509   0.546859  0.538538

If you have questions about using a function, you can always type the question mark ? in your REPL following the function name:

julia> ?rand

Package Manager & Environments

Julia carries its own package manager. You can use it as a normal package:

julia> using Pkg

To install a package, you can use:

julia> Pkg.add("Bloqade")

To remove a package, you can use:

julia> Pkg.rm("Bloqade")

All Julia programs run inside an environment. The default is the global environment. It is usually recommended to run your notebook in a local environment, so you won't hit any version conflicts between different packages.


For more resources, check the official website