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 typesafe 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 = 1
1
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.
Functions
In Julia, you can also define short-form, one-line functions using =
(assignment) similar to how you write things mathematically.
julia> f(x) = 2x
f (generic function with 1 method)
Typing the function's name gives information about the function. To call it, we must use parentheses:
julia> f
f (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 end
g (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 = 0
0
julia> for i in 1:10 s += 1 end
we can now check the value of s
by typing it again:
julia> s
10
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 else # do something end
Matrix and Array
Julia carries its own Array
type. If you use Python, it is similar to numpy.array
in Python except:
- index starts from 1,
- the multi-dimensional index is column-wise.
You can also use list comprehension:
julia> [i for i in 1:10]
10-element Vector{Int64}: 1 2 3 4 5 6 7 8 9 10
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.88506 0.373074 0.272312 0.815668 0.835941 0.717028 0.501422 0.292752 0.883866 0.371989 0.88556 0.606658 0.153943 0.222886 0.657957 0.404739 0.14584 0.645776 0.886412 0.428938 0.0155723 0.940652 0.661818 0.0856018 0.58294
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.
Resources
For more resources, check the official website julialang.org/learning: