TL;DR tips for Julia users coming from Matlab

While the Julia Discourse and Slack communities are incredibly helpful, the responses can lean more technical. I thought it’d be nice to collect TL;DR advice for Julia summarized in simple terms.

For example, in this thread on “why use tuples”, in addition to the responses about tuples helping with type inference, it’d be nice to also have a simple “they are faster than arrays (e.g., to splat)”. Non-technical terminology is also helpful for folks like myself, who are transitioning from Matlab or low-level C/C++ and don’t often think about issues like type stability. Julia syntax was easy to get used to, but it can be tough to search for answers when you don’t know technical terminology.

@brenhinkeller’s Julia for Matlab programmers provides several nice non-technical TL;DR summaries. For example, advice for getting started with @code_warntype:

Use @code_warntype followed by a function call to check for type-stability (anywhere it prints a red Any is bad). Type-stable code is, like, two orders of magnitude faster than type-unstable code (though even type-unstable code may be faster than Python :p)

I learned this way that you can just use @code_warntype to find for type instability by looking at which variables are red at the top.

Any other TL;DR advice you’ve found useful as you’ve learned Julia?

4 Likes

A few examples from my own experience:

  • tuples are fast to splat, arrays are not
  • use tuples when you can for speed, and StaticArrays when you need tuple-like speeds but an Array type
  • you can discard extra function outputs via a,_ = foo(...)
  • type annotation in functions doesn’t make code run or compile faster or help with type stability, it’s mostly for specializing in multiple dispatch
  • a LOT of functionality I was looking for was already in Base. Look there before trying external packages because Base functions are really fast.
  • be wary of old (pre-2018) Julia answers on StackExchange
4 Likes

Just now from Slack: if x is a tuple of arrays, map(zero,x) makes a tuple of zero array.

I can’t believe it took me so long to learn that. I was using the type unstable ntuple(a->zeros(size(x[1])),length(x)) before.

1 Like

I am not sure this is true. Do you have an example that shows some slowdown when doing this?

2 Likes

I thought I did, but I might have conflated it another type instability. I forgot where I saw this, but let me try to put together a MWE and check again.

Removed that point from original comment for now.

I don’t think the issue with arrays is mutability. The point is that the length of tuples is known at compile time and so which method to call can be figured out at compile time. For arrays, the length is unknown at compile time and so when splatting arrays you incur dispatch overhead.

3 Likes

exp(A), sin(A), etc. are functions of matrix argument. Use exp.(A) to apply element-wise (like matlab).

fft(A) operates on all dimensions of A. Use fft(A,1) to apply to columns (like Matlab).

Trivial, but the piping operator can produce super readable code at times.

# power spectral density to impulse response
h = S .|> sqrt |> fftshift |> ifft |> fftshift
4 Likes

For some programming languages, knowing a similar language can be a useful starting point — even if they are different, various fundamental concepts and techniques carry over nicely. Specifically, knowing some Python, R, (Common) Lisp, Scheme, or even Ruby can be helpful when learning Julia.

Matlab is the odd one out — despite some similarities in surface syntax, it is very different from Julia in terms of design. Matlab seems to encourage a lot of bad habits and kludges, leading to unstructured and ad hoc solutions.

Because of this, I would suggest that former Matlab users who are not experienced with any of the above languages just treat Julia as a completely new language instead of trying to “transition” from Matlab. There are so many things to unlearn that it may be less confusing to just start fresh, as if learning programming for the first time. Read the manual, look at code, ask questions here and it will work out fine.

6 Likes

I thought that immutability implied that you know the length of tuples at compile time?

Is there an example of an immutable container whose length can change?

Immutability means that you cannot change the fields of your object after creation, but object creation happens at runtime so that does not help the compiler determine the length of your collection. The reason why splatting for tuples is efficient is that the length of a tuple is part of its type, so it is accessible to the compiler.

1 Like

You cannot have an immutable container with changing lengths, but you can have mutable containers with fixed lengths (e.g. StaticArray.MArray) and splatting is still efficient for such types.

2 Likes

You can automatically translate Matlab to Julia w/:

Repo here: GitHub - lakras/matlab-to-julia: Translates MATLAB source code into Julia. Can be accessed here: https://lakras.github.io/matlab-to-julia (last updated April 2019)

MATLAB–Python–Julia cheatsheet: https://cheatsheets.quantecon.org/
Noteworthy differences: https://docs.julialang.org/en/latest/manual/noteworthy-differences/index.html

2 Likes

One more I just remembered: the Julia manual is fantastic, but it also gets updated fairly regularly. Don’t just read it once, go back and read it again (or at least look for updates).

1 Like

@jlchan
Also check JuliaMatlab organization

You may be interested in Native Julia noteworthy differences from MATLAB · MatLang

Feel free to make a pull request adding your tips. I will review them and will add them to the repository if they are accurate.

I made an issue for @brenhinkeller’s repo too.
https://github.com/brenhinkeller/JuliaAdviceForMatlabProgrammers/issues/1

1 Like

Thanks, but I’m not looking for Matlab-to-Julia translations. I’m trying to identify and fix the more common “bad habits and kludges” (as @Tamas_Papp referred to them) that come from experience programming in Matlab.

The manual is great, but I’ve found myself reading over parts without realizing that they applied to my current situations. I’ve found TL;DR explanations to be helpful in knowing where to look for information, not to replace more precise technical explanations.

Because of this, I would suggest that former Matlab users who are not experienced with any of the above languages just treat Julia as a completely new language instead of trying to “transition” from Matlab. There are so many things to unlearn that it may be less confusing to just start fresh, as if learning programming for the first time. Read the manual, look at code, ask questions here and it will work out fine.

Not sure how practical this is for some people. I feel lucky as an academic to be comfortable enough productivity-wise to devote a fair amount of time to learning Julia, and even then I had to learn a lot on the fly in order to stay productive in my projects.

There will always be bad habits to unlearn, even if you treat Julia as a new language. I’ve been perfectly happy to trade Matlab for the Julian way of thinking, but it’s hard to catch unconscious bad habits unless you know what to look out for.

If Matlab is your first and maybe only language, I think it’s difficult to do this. Your habits can be ingrained, and probably even ‘invisible’ to yourself, so you will just drag along a lot of assumptions without knowing that they are Matlab-related, and without even knowing that they are assumptions.

I think it can be useful sometimes to make them ‘visible’ by pointing them out. One mantra I kept in the back of my mind was “don’t be afraid of loops”. It required deliberate distancing from Matlab to keep the ‘vectorize everything’ mindset away, because it was second nature, and permeated every part of my algorithmic thinking.

3 Likes

Another difference is, that any Matlab vector is just a row or column matrix. In Julia however there’s the vector (or 1d Array) type which would by default correspond to a column vector. A row vector can be obtained by transposing with ' which wraps the type in an Adjoint{} type. That Julia style made me a lot things easyer and you don’ t need to do things like dimension checks that often.

The difference in the scope of variables made the biggest difference to me. Since all variables in Matlab are kind of global, the structure of code adapts to that. Loops and blocks and functions in Julia are like fight club. Nothing outside will know what happens inside (except you want to). I had some time to adapt to this but I think it makes for better code.

Last but not least, you should make use of generators and maps. They are simply great! Wherever you get stuck in matlab, one of those would be the solution. Also do not be afraid of loops and custom types.

One thing I notice with people coming from other languages (Matlab, but also R) is writing large, monolithic functions that are not easy to test and debug. Related to this, users of languages which do not make a functional style convenient underutilize higher order functions.

That said, I am skeptical that having a list of bad habits is very helpful. It is more about learning new things, which is a very gradual and iterative process. Reflecting on and refactoring one’s code occasionally is part of the learning process.

I also learned a lot from reading code by others. Practically, the best opportunity for this is making a PR, which forces one to understand existing code. I agree that it is time-consuming, but it is an investment that pays off.

3 Likes

The example x = f(x) by itself might be oversimplified. I see type instability when redefining variable names using anonymous functions

function not_type_stable(a)
    a = (x->x[1:2,:])(a)
    b = randn(2,4)
    c = randn(2,4)
    c = (x->@. a*x).((b,c))
    return c
end
@code_warntype not_type_stable(rand(4,4))
function type_stable(a)
    a2 = (x->x[1:2,:])(a)
    b = randn(2,4)
    c = randn(2,4)
    d = (x->@. a2*x).((b,c))
    return d
end
@code_warntype type_stable(rand(4,4))

with slowdown for not_type_stable

A = rand(4,4)
@btime not_type_stable($A);
  1.310 μs (12 allocations: 880 bytes)
@btime type_stable($A);
  301.987 ns (7 allocations: 768 bytes)