100 Languages Speedrun: Episode 19: Julia

taw

Tomasz Wegrzanowski

Posted on December 10, 2021

100 Languages Speedrun: Episode 19: Julia

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!")
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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])
Enter fullscreen mode Exit fullscreen mode

Given that it's a new language, it handles basic Unicode operations correctly as expected:

$ julia unicode.jl
[5, 4, 1]
["HELLO", "ŻÓŁW", "🍩"]
Enter fullscreen mode Exit fullscreen mode

Also notice Python-style list comprehensions.

More Unicode

Take a guess which letter it would print:

s = "Żółw"
println(s[3])
Enter fullscreen mode Exit fullscreen mode

Did you guess correctly?

$ julia unicode2.jl
ó
Enter fullscreen mode Exit fullscreen mode

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]=>'ó'
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
)
Enter fullscreen mode Exit fullscreen mode

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])
Enter fullscreen mode Exit fullscreen mode

Which outputs:

$ julia dot.jl
42
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
[11, 12, 13]
Enter fullscreen mode Exit fullscreen mode

.+ 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)
Enter fullscreen mode Exit fullscreen mode

Which outputs:

true
true
1.4999999999999998
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Which works just as expected:

$ julia macro.jl
Enter number:
69
69 is odd
$ julia macro.jl
Enter number:
420
420 is even
Enter fullscreen mode Exit fullscreen mode

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.

Code for the Julia episode is available here.

💖 💪 🙅 🚩
taw
Tomasz Wegrzanowski

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