Tomasz Wegrzanowski
Posted on December 10, 2021
Julia might be the newest language covered so far, released just in 2015, and meant to replace older (and generally awful) languages for scientific computations. Let's see how well it does.
As an interesting side note, Jupyter Notebook is named after Julia, Python, and R, even though in practice it's used for Python, Python, and Python. I guess PyPyPy Notebook just doesn't sound right.
Hello, World!
Nothing surprising here:
println("Hello, World!")
Fibonacci
function fib(n)
if n <= 2
1
else
fib(n-1) + fib(n-2)
end
end
for i=1:30
println(fib(i))
end
The syntax is very close to Ruby and Python. Functions don't need explicit return
.
FizzBuzz
function fizzbuzz(n)
if n % 15 == 0
"FizzBuzz"
elseif n % 5 == 0
"Buzz"
elseif n % 3 == 0
"Fizz"
else
n
end
end
for i=1:100
println(fizzbuzz(i))
end
Everything works just fine, and is perfectly clear. Time to do something a bit harder.
Matrices
Julia has builtin support for vectors and matrices, so we can do very fast Fibonacci formula with them, similar to what we did with Octave.
u = [1 1]
m = [1 1; 1 0]
for i=1:40
result = (u * (m ^ (i-1)))[2]
println("fib($i) = $result")
end
Which generates what we expect:
$ julia fibmatrix.jl
fib(1) = 1
fib(2) = 1
fib(3) = 2
fib(4) = 3
fib(5) = 5
fib(6) = 8
fib(7) = 13
fib(8) = 21
fib(9) = 34
fib(10) = 55
fib(11) = 89
fib(12) = 144
fib(13) = 233
fib(14) = 377
fib(15) = 610
fib(16) = 987
fib(17) = 1597
fib(18) = 2584
fib(19) = 4181
fib(20) = 6765
fib(21) = 10946
fib(22) = 17711
fib(23) = 28657
fib(24) = 46368
fib(25) = 75025
fib(26) = 121393
fib(27) = 196418
fib(28) = 317811
fib(29) = 514229
fib(30) = 832040
fib(31) = 1346269
fib(32) = 2178309
fib(33) = 3524578
fib(34) = 5702887
fib(35) = 9227465
fib(36) = 14930352
fib(37) = 24157817
fib(38) = 39088169
fib(39) = 63245986
fib(40) = 102334155
Vectors and matrices use 1-based indexing, which is totally cringe, even if that's what pre-computer-era mathematicians are used to.
Julia supports string interpolation with "$x"
for variables and "$(...)"
for expressions. It's somewhat disappointing that we finally arrived in the world where every sensible language supports string interpolation, but every single one of them chose different syntax for it.
Unicode
strings = ["Hello", "Żółw", "🍩"]
println([length(s) for s in strings])
println([uppercase(s) for s in strings])
Given that it's a new language, it handles basic Unicode operations correctly as expected:
$ julia unicode.jl
[5, 4, 1]
["HELLO", "ŻÓŁW", "🍩"]
Also notice Python-style list comprehensions.
More Unicode
Take a guess which letter it would print:
s = "Żółw"
println(s[3])
Did you guess correctly?
$ julia unicode2.jl
ó
In every sensible language that would be w
. Even JavaScript gets that right, and it rarely gets anything right.
I already mentioned that Julia uses 1-based indexing, even for strings, so you might have guessed ł
instead, but how the hell did we get w
?
It turns out that Julia indexes characters by their byte position, starting at 1. So for string "Żółw"
, these are the letters:
-
"Żółw"[1]
is'Ż'
-
"Żółw"[3]
is'ó'
-
"Żółw"[5]
is'ł'
-
"Żółw"[7]
is'w'
And every other index raises an exception like:
julia> "Żółw"[2]
ERROR: StringIndexError: invalid index [2], valid nearby indices [1]=>'Ż', [3]=>'ó'
We can call collect(eachindex("Żółw"))
to get a list of the valid indices [1, 3, 5, 7]
.
Overall, I'm not sure if this is better or worse than JavaScript.
Equality
println([1 2] == [1 2])
println([1 2; 3 4] == [1 2; 3 4])
println((1,2) == (1,2))
println((10:20) == (10:20))
It's not much surprise that ==
works correctly on all complex types like vectors, matrices, tuples, ranges, and so on:
$ julia equality.jl
true
true
true
true
Blocks
Julia supports many different styles of functional programming:
numbers = (1:10)
println(
[x for x in numbers if x % 2 == 0]
)
println(
filter(iseven, numbers)
)
println(
filter(x->x%2 == 0, numbers)
)
println(
filter(numbers) do x
x%2 == 0
end
)
Notice that block argument goes last with do
syntax, but it's translated to be first argument of the function.
Dot syntax
Like other mathematical programs, operators can be applied to collections element-wise. Julia also provides convenient syntax to use it with your own functions:
double(x) = 2*x
println(double(21))
println(double.(1:10))
println(10 .+ [1,2,3])
Which outputs:
$ julia dot.jl
42
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
[11, 12, 13]
.+
and .()
are like implicit map
for the appropriate types. This isn't something most general-purpose languages typically do, but it's much more common in mathematical code, so many mathematical languages have some degree of support for this.
Unicode operators
I mentioned back in Emojicode episode that languages really torture their ASCII symbols to avoid using anything outside ASCII. Operators often consist of 2-3 symbols, and the same combination is often overloaded to mean a lot of different things. Julia takes modest steps to use Unicode mathematical symbols.
# is subset
println([1,2] ⊆ [2,3,1])
# is element
println(3 ∈ [2,3,1])
# some trigonometry and square roots
println(sin(π/3) * √3)
Which outputs:
true
true
1.4999999999999998
Julia doesn't go overboard, I think in the future this use will become more common.
Macros
Unusually for a non-Lisp, Julia has macros. They don't look quite the same as regular Julia syntax, but it's a thing:
macro unless(condition, expression)
quote
if !($condition)
$expression
end
end |> esc
end
println("Enter number:")
num = parse(Int, readline())
if isodd(num)
println("$num is odd")
end
@unless isodd(num) begin
println("$num is even")
end
Which works just as expected:
$ julia macro.jl
Enter number:
69
69 is odd
$ julia macro.jl
Enter number:
420
420 is even
I don't know how much that matters in practice. A number of non-Lisp languages tried that, and none of those attempts really did much. Julia in general is very generous with the syntax. quote...end
with $
is a "quasi-quote" in Lisp terms. |>
is just Elixir style piping into another function and not specific to macros. You can do -42 |> abs
if for some reason you don't want to type abs(-42)
.
As you can see as Julia isn't a Lisp, syntax introduced by macros doesn't look quite the same - there's extra @
and begin
there. But maybe it would be good enough for some use cases.
Should you use Julia?
For the kinds of tasks Julia is aiming at, I'd generally recommend trying out Python first, but Julia isn't a bad choice.
It's a fairly modern language with limited baggage, friendly syntax similar to Python and Ruby, and you can use the familiar Jupyter Notebook style of development. Many new languages like Go, Rust, and TypeScript were obviously created by mental boomers enamored with distant past, and who rejected many of the lessons of modern programming language design. Julia doesn't feel like it - it feels like they got most of the things right, given what they were aiming for.
Most other "mathematical" languages have terrible quirks, massive baggage, awful syntax, are seriously underpowered once you step outside their core domain, and are often proprietary or semi-proprietary. By comparison, Julia does decently on all these criteria. If together with Python they ends up euthanizing some crappy closed source languages still used in the science world, that would make the world a better place.
I didn't investigate Julia's performance at all. Honestly I think the whole "performance" stuff is really overrated, and microbenchmarks are completely without any real world significance, but if you're into such things, Julia's performance claims are here.
Overall, I think it's highly promising, even if I'd probably still try Python first.
Code
All code examples for the series will be in this repository.
Posted on December 10, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
March 1, 2023