Julia: Fast as Fortran, easy as Python

Tech-Speak

Julia

The Julia [4] language was intended to offer an alternative to the two-language problem. It has an elaborate type system but can be used as a simple, dynamically typed language with an intuitive, expressive syntax. It has a REPL with helpful features, such as built-in help and package management systems. It can interface directly with graphics libraries, as you'll see, to produce scientific visualizations directly from your code; yet, it achieves execution speeds on par with Fortran and C [5].

Since its first beta release in 2012, these qualities have attracted a growing community of numerical scientists to the young language. Naturally, Julia love is not unanimous, but the general agreement is that it has met its ambitious goals and represents the fulfillment of a long-held desire for a language that is as fast as Fortran but as easy as Python.

Given that, Julia's first stable release version 1.0 in August 2018 was a milestone for the technical computing community. The main significance of this release is a promise of language and API stability for the long term, allowing users to undertake major projects, knowing that changes to the language will not require any changes to code under development. The original language design arose from academic computer science research at MIT conducted by Alan Edelman, Stefan Karpinski, Jeff Bezanson, and Viral Shah.

Julia Syntax

This article will not be a complete tutorial for the Julia language, but I will demonstrate the main language features and give you a feel for its syntax, which should allow you to get started experimenting with the language and give you a good idea of whether you might want to consider adopting it for your next project.

First, you need to install Julia. To get the latest version, go to Julia headquarters [4] and click on the prominent Download button. At the time of writing, that got me version 1.0.1, on which the code examples in this article, executed on a Linux laptop, are based. A source download and binaries are available for Linux, macOS, Windows, and BSD.

Installation from the binary packages is simple, because all of the dependencies to get started are included. After installing, type julia at the terminal; you will see a welcome message, and your shell prompt should change to julia> . Now you can type Julia expressions and see the results. Try typing 1 + 1; or, type 1 + "s" to see what a type error looks like.

Those examples used numbers and strings. Before getting deeper into syntax, I'll take a brief look at Julia's other data types. Strings, enclosed in double quotation marks, are made up of sequences of characters. Individual characters are enclosed in single quotes (e.g., 's'). Therefore, "st" is a valid string, but 'st' is a syntax error. The operator for string catenation is *, which is one of Julia's syntax oddities, but one for which they have a detailed, theoretical argument (which need not concern us here). You can stick strings and characters together to get longer strings; for example, "str" * 'i' * "ng" yields "string".

Julia has several types of numbers. In addition to integers, you can compute with floating point numbers, integers with arbitrary precision, complex numbers, and rational numbers (notated <numerator>//<denominator>). You must know how your language of choice handles integers, as shown in Table 1, to avoid the "integer division pitfall."

Table 1

Integer Division

      Result
Operation Fortran C Python 2 Python 3 Julia JavaScript
1/2 0 0 0 0.5 0.5 0.5

Listing 1 contains part of a Julia interactive session that shows how Julia's rational numbers work. One interesting feature of this number type is that Julia represents each rational number internally in its reduced form, which explains the results of the call to the numerator() function.

Listing 1

Interactive Julia

julia> 1//2 + 1//3
5//6
julia> numerator(10//12)
5
julia> 2//3 == 4//6
true
julia> 2//3 == 4/6
false

After strings and the various types of numbers, the most important built-in data type in Julia is the array. Any significant computation in the language will involve extensive use and manipulation of arrays, and Julia is designed to make this easy and expressive. Arrays are defined by square brackets, and you can glue them together horizontally with a space or vertically with a semicolon, as shown in Listing 2 (ending a line with a semicolon suppresses the printing of the result in the REPL).

Listing 2

Arrays

julia> a = [1 2];
1x2 Array{Int64,2}:
 1  2
julia> b = [3 4];
1x2 Array{Int64,2}:
 3  4
julia> [a b]
1x4 Array{Int64,2}:
 1  2  3  4
julia> [a; b]
2x2 Array{Int64,2}:
 1  2
 3  4
julia> [a' b']
2x2 Array{Int64,2}:
 1  3
 2  4

In Listing 2, note that the prime operator (') transposes an array. Julia has many operators that act on arrays as a whole. Some of the most useful ones are reshape(A, dimens), which reforms the elements of A into a new shape specified by dimens; rand(dimens), which creates a new array and fills it with random numbers; and range(start, stop=stop, length=n), a convenient way to create an evenly spaced set of numbers, which is useful in graphing. Additionally, Julia's linear algebra library provides a host of mathematical operations on 2D arrays, providing ways to factor, calculate eigenvalues and eigenvectors, perform inverses, and do all the usual math on matrices.

The ability to operate on arrays as units or extend operations elementwise over an array is a part of the language syntax that allows you to express many complicated calculations without writing loops. Julia uses a dot (.) that extends a function or binary operator to operate element-by-element over an array. For example, a .+ b returns the array [4 6], which is [1+3 2+4], and a .* b gets you [3 8] ([1*3 2*4]). However, if you omit the dot, you will get the matrix product of the two arrays; so a * b' returns a matrix with the single element 11.

Julia uses the dot syntax to extend any function, so it can operate efficiently over arrays – and this extends to functions that you define yourself. For example, sin.(a) returns the array [0.841471 0.909297], which is [sin(1) sin(2)]. If you chain together dotted functions, as in the expression cos.(sin.(a)), the Julia compiler "fuses" the operations: Rather than applying the sine function to the array, and then the cosine function to the resulting array, the combined function is applied once per array element. This strategy is one of many that the compiler applies to make array operations space and time efficient, and it works with any combination of operators and built-in or user-created functions.

Julia has several other major built-in data types familiar from other modern languages, including tuples and dictionaries, but it also goes further and offers a flexible and sophisticated type system that includes user-defined types. These can be as elaborate as you like and can inherit behaviors from built-in or other user-defined types, yet they are treated equivalently to built-in types by the compiler, which means their use does not hinder optimization.

Type declarations are optional in Julia. They can sometimes help the compiler optimize your code but are generally not required for this purpose. Julia is strictly typed, but the compiler will infer the types of your variables if you do not declare them. As you design your program, you will mainly use types to organize your code and data and to express your algorithm more naturally, rather than to help the compiler generate more efficient machine instructions. As a beginner with the language, you can certainly get by with a sparse use of declarations involving only the built-in types and learn how to employ the more advanced capabilities of the type system as you gain expertise.

Listing 3 shows Julia's basic control structures, most of which are fairly conventional. Indentation is for convention and readability and is not part of the syntax. The end statements that terminate each block are required, however.

Listing 3

Control Structures

# while loop
i = 1;
while i < 7
  print(i^2, "; ")
  global i = i + 2
end
# for loop using iterators
for n in 1:5
  print(2^n, "--")
end
# implied nested loop
for i = 1:2, j = 1:2
  println([i, j])
end
# loop over container
for i in ["first", "second", "third"]
  println(i)
end

The four examples are annotated with the # syntax for a single-line comment (multiline comments start with #= and end with =#). The results of most of these examples should be easy to predict, but the best way to learn this or any language is, of course, to try things out yourself.

One quirk is illustrated in the first example: Blocks in Julia create a local scope, so variables need to be distinguished with the global or local keywords.

The implied nested loop shown in Listing 3 is a convenient syntax shortcut that removes the need for repetitive and deeply nested loop structures when looping over multiple variables, something ubiquitous in numerical code.

Unicode

Almost all languages in wide use today were created before the advent of Unicode, the universal standard that finally allowed working with computers in any of the world's writing systems and included a host of special symbols. Because of this history, most existing languages have at best an awkward relationship with the world of Unicode. Those that have mature Unicode support have gained it though a painful process of radical language revision, as in the transition from Python 2 to Python 3.

Julia has the advantage of having been designed after Unicode was universally accepted, and it embraces this powerful and liberating technology fully by defining characters and strings in terms of Unicode by default. You are also free to include any Unicode characters in the names of variables, functions, and operators, and some of Julia's built-in operators have Unicode versions to match the symbols used in mathematical writing. For example, you can write the final example in Listing 3 as

with the mathematical symbol for membership in a set. If you have no convenient way to enter this symbol, you can take advantage of another of the REPL's special features: support for entering special symbols with LaTeX syntax. To get this symbol, type \in and press the Tab key (\in<TAB>), and the REPL inserts

for you.

The ability to construct variable names out of Unicode allows you to incorporate subscripts and superscripts into variable names, so you're able to make fewer typographic compromises when translating mathematical formulas into computer code. Another detail of Julia's syntax interprets a number prepended to a variable to signify multiplication, just as in conventional mathematical notation, with no explicit multiplication sign required. Combining these typographic conveniences allows you to write legal Julia expressions such as

by using LaTeX notation \alpha<TAB>\_1<TAB> and \beta<TAB>\_2<TAB> for the Greek letters with subscripts.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy ADMIN Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

comments powered by Disqus