Manhattan Metric

Software and Science in Equal Measure

Five (More) Reasons to Check Out Julia

As 2018 gets under way, chances are that if you’ve heard of
Julia you know of it as a programming language for
science, finance, and AI research. It is true that each of these fields
have adopted Julia and benefitted greatly from its features, but I feel that
Julia represents an interesting, new approach to programming that could improve
the way we all write code. Much has already been written (especially in the
official documentation) about Julia’s
take on types, optional typing of functions, and organization around type-based
multiple dispatch. With this post, I want to look at five lesser known features
that I believe make Julia a joy to work with, even as a generalist developer.

#1: do…end

Among the languages that inspired Julia is Ruby, so it should come as no
surprise that Julia adopted one of Ruby’s more iconic features: the do...end
block. As is the case with many other features that Julia has adopted, its
implementation of do...end is simpler and more flexible than Ruby’s.

In Ruby using do...end creates a Block object that is callable by invoking the
yield keyword or is made available as an argument to the method if the
terminal method parameter is decorated with an &. Julia instead defines an
anonymous function from the do...end block and passes it as the first argument
to the immediately preceding function call in all cases. Since Julia, unlike
Ruby (but like Python), can operate on functions directly, this works quite
simply.

One other slight difference with blocks in Ruby is the dropping of the pipe
characters to delimit the block’s parameters. Instead, everything from do to
the end of the line is parsed as the block’s parameter list. This does mean that
single line do...end blocks are not possible (though they were always
uncomfortable, at best, in Ruby). Julia makes up for this by also adopting an
anonymous function literal form almost identical to Java 8’s.

julia>function mymap(f,coll)println(typeof(f))map(f,coll)endmymap(genericfunction with1method)# With a `do...end` blockjulia>mymap(range(1,4))doxx*2end##1#24-elementArray{Int64,1}:2468# Using an anonymous functionjulia>mymap(x->x*2,range(1,4))##3#44-elementArray{Int64,1}:2468# Passing a function directlyjulia>double(x)=x*2double(genericfunction with1method)julia>mymap(double,range(1,4))#double4-elementArray{Int64,1}:2468

(Note that ##1#2 is just Julia’s way of representing anonymous functions.)

All of these forms are equivalent and, since the release of Julia v0.6, perform
identically.

#2: Unicode Operators

For many programming languages, support of the full Unicode character set in
identifiers (variable and function names) is useful for little more than the
occasional joke or obfuscated code competition. Julia, on the other hand, has
done an admirable job of supporting Unicode, not only in identifiers but also,
for certain characters, as operators.

To understand why this is an advantage, consider the case of division. In Ruby,
Python, and even C, the value of 4 / 3 is 1! If you want something closer to
the actual value of 4/3, one or both of the values must be a floating point
number to start: 4 / 3.0 results in 1.3333.... One might argue that this is
a long-standing tradition in programming languages, and so it’s “no big deal”,
but it’s wrong and complicated and leads to bugs. If, instead of number
literals, you’re evaluating a / b you can’t know the type of the result
without knowing the explicit type of each argument.

Julia avoids this whole morass by having two division operators: / and ÷.
Using / will always give the closest floating point approximation of the
division, even if both operands are Integer types going in. The thought being
that this is most often what programmers expect from a / b. Using ÷ performs
truncated Integer division. (It’s worth noting that there are two more
division operators: // which performs Rational division, and \ that
performs a left division.)

Of course, Unicode operators aren’t going to be much use if you have to go
hunting in some Emoji pallet each time you want to insert one. Here, Julia is
also extremely helpful. At the REPL, inserting a Unicode character is as simple
as entering a \ followed by the symbol’s abbreviation, and pressing <TAB>.
So for the division example above, you’d start with 4 \div, press <TAB>, and
then enter 3 for: 4 ÷ 3. The Julia packages for Vim, Emacs, Atom, and more
(all available from the excellent
JuliaEditorSupport GitHub organization)
support the same ability so that entering Unicode characters in your editor
should work the same as the REPL.

#3: dot Broadcast

Even though Julia is much more generally useful for non-numeric programming
tasks than languages such as R and Matlab, it does still include a number of
features that make working with matrices convenient (such as being able to
trivially transpose a matrix with the ' operator). In keeping with Julia’s
design philosophy, though, even these conveniences are simply implemented in the
most flexible way possible (to see just how flexible, consider a recent
proposal to repurpose the transpositon operator for method
currying).

The most recent example of a feature originally intended for numeric programming
that turned out to be more generally useful is that of the “dot” broadcast. If
you’re familiar with the functional programming concept of map, then
broadcast will seem familiar…but different. The essential difference between
map and broadcast is that when broadcast is applied to combinations of
iterable and scalar values, it effectively repeats the scalars to match the
dimensions of the iterables. (This will become clearer, I hope, with some
examples below.)

Julia has had a broadcast method for quite some time. What was added recently
was the ability to turn any operator or any function into a broadcast function
with the addition of a humble .. For functions, the . goes between the
function name and the open parentheses. For operators, the . goes before the
operator. To understand why broadcasting is important, consider a simple
function to square a value:

Squaring a matrix is a very different operation than squaring each value in a
matrix. Broadcasting the square function allows us to easily do the later.
Bringing scalars into the mix, we can see the clear difference between
broadcasting and maping:

The true power of the dot broadcast comes, however, when you begin forming
chains. It takes a bit of getting used to, but it has the potential to
drastically simplify any code you write that operates on collections.

#4: Pkg.generate()

There is a good reason that, despite it’s relatively young age and modest
community size, Julia already sports a healthy package ecosystem. It is because
Julia has the development, distribution, and support of packages at its heart.
Out of the box, Julia includes the notion of downloading and installing 3rd
party packages. It will even helpfully tell you the first time you try using
Foo that you probably first need to run Pkg.add("Foo").

What really makes Julia special is that the default Pkg module is extended by
the PkgDev package to include facilities for developing new packages.

Notice that this doesn’t just generate a project skeleton, but prepopulates that
skeleton with files to manage Git ignores, CI for macOS, Linux, and Windows,
and code coverage metrics. For this reason, it should come as no surprise that
Julia packages tend to be extensively tested with significant coverage. Newly
generated packages are also automatically placed in the correct location to be
used immediately.

If you happen upon a bug in someone else’s package, Julia also makes it easy to
contribute back.

Calling checkout will temporarily decouple the package in question from the
normal dependency and version resolution process, so that you can make whatever
changes are necessary and submit a pull-request with your fixes. Once that’s
done, calling Pkg.free("Requests") returns the package to normal version
control.

#5: String macros

One of the more powerful and under appreciated features of other LISPs is the
concept of a reader macro. These allow a developer to manipulate how the
language is parsed at a fundamental level, enabling the creation of custom
literal forms. While Julia doesn’t have proper reader macros, it does have the
next best thing: string macros.

Essentially how these work is that, if you define a macro whose name ends in
_str, then it can be used to decorate a string literal. Instead of parsing
that literal directly as a string, the string is first passed to the
corresponding macro and whatever type it returns is substituted in place.

In fact, although Julia’s use of r"foo.*bar"i to construct Regular Expression
literals is very reminiscent of Python, this is implemented as a simple string
macro, not a parser hack. A number of Julia packages have already made great use
of this feature. For example, the Cxx.jl package has a string macro for
compiling C++ code within a Julia function.

Bonus Reason

Hopefully by now I’ve convinced you to give Julia another look as 2018 gets
underway, but if you need some additional motivation, here’s one more reason: it
is highly likely that Julia will hit v1.0 in 2018.

Predicting software milestones is notoriously fraught, doubly so when it’s open
source. That said, Julia is well into the home stretch. One more pre-1.0 version
will be released (v0.7) with all the same features as v1.0 but retaining
deprecation warnings for everything that’s changed since v0.6. Then, once
everyone has had a chance to validate their packages and eliminate deprecated
code, the warnings will be removed and v1.0 will be christened…or, at least
that’s the plan.

The core team have announced their intention to adhere strictly to semantic
versioning of Julia, so any library developed against v1.0 will continue to work
for the duration of Julia v1.X’s lifetime. This does not mean that v1.0 will be
perfect, but it will be stable. In fact, in a number of instances the core team
has opted not to include features in v1.0 with the reasoning that it is easier
to add features in v1.1 than to have to wait until v2.0 to remove them.

The Julia community is still on the small side, but it is growing. There is a
healthy collection of libraries already, and relatively simple means for
incorporating libraries from C, Python, and Java (and slightly less simple means
for calling out to C++). If you’re looking for a new language to learn in 2018
that will both help you grow as a programmer and provide an opportunity for you
to have a non-trivial impact on the future of the language, Julia is definitely
worth a look!