The issue of the scoping rules of top-level
for loops (and their kind) revives from time to time, and it seems that this will continue happening for a while; so I thought that it might be useful to share some thoughts and positive experiences about this in the context of explaining Julia to new users. Hopefully these tips may help others to prevent that this particular feature of Julia (even as it is now, without the “first-hit” rule) becomes a barrier for evangelizing about Julia.
Tip #1: Wrap everything in functions
I know, I know; this has been suggested and contested over and over again. It’s the most effective solution to avoid issues with the global scope, and also good programming practice; but this doesn’t go well with quick and dirty experiments, trying out code snippets interactively, and that kind things that are typically done when one learns a new programming language…
… Or it didn’t go well with those things until a couple of months ago. The new debugger works like a charm, and I can tell by experience that some newcomers love to play with it, specially in the nice interface that Juno provides: put breakpoints here and there, stop now and then continue, peek into the functions you are calling… That’s even more fun than the good old copying and pasting code into the REPL - The only frequent complaint is having to write
Juno.@enter to go into debugging. (I acknowledge that the people I work with are mostly from Matlab background, so perhaps this is a biased experience.)
Thanks to this, wrapping your code in functions becomes more attractive even for the initial experiments, since that is the most convenient way to debug it.
Tip #2: introduce
for loops with mutable objects
This is the other obvious way to avoid hitting the global scope issue. The typical problem is with:
x = 1 for i = 1:10 x += i end
This doesn’t work in the top level, but of course this does:
x = ones(10) for i = 1:10 x[i] += i end
In your first steps with Julia perhaps you want to try the exciting experience of writing out explicit loops and running them fast as light (again, biased opinion from Matlab users who usually had to strive with bizarre vectorized code to avoid the slowdown of loops in that language). You may want to do this even before writing functions, so the tip #1 does not help here.
But often loops have the purpose of filling out arrays or similar objects that you can mutate without having to replace them, thus working around the global vs. local scope issue. This is a convenient way of “hiding” the problem, at least when you can control the examples you use to show how Julia works to your peers or to students.
Tip #3: “loops are like functions”
You may be lucky and hide the issue for some time with the two previous tips, but your unfortunate user will eventually hit it, so you should prepare him or her before the problem is encountered. Excellent explanations about scopes, etc. are not allowed: your colleague or student doesn’t want to learn about that, only wants code that works, and some shallow understanding that doesn’t take more than a couple of minutes to learn.
But the two previous tips may also come in handy to prepare the simple explanation that beginners need about this issue. You can show how much faster is the code that is encapsulated in (good-written) functions, thanks to the just-in-time compilation; evaluating the slowdown introduced by the debug mode may also help to understand it.
And if the user - specially if accustomed to the slow loops of other languages - wonders about the speed of such loops in Julia, you can explain that this is also thanks to the just-in-time compilation of the code that lives inside loops.
Now, I know that this statement is not accurate, but for the sake of simplicity you can tell that Julia treats the code inside the loops as it does with the code of functions, and therefore the relationship between the variables inside and outside the loop are like those that are seen in functions.
It is not necessary to go deep into those relationships. Matlab, Python and other languages force you to qualify variables as
global if they are defined outside the functions but used inside them - and this is learnt by people without any pain, although their understanding about scopes may be only partial or inaccurate. So the only extra bit that has to be learnt for Julia is that this has to be done too - but not only in functions, but also in loops - unless the loop itself is inside a function, as seen before.
In summary, my suggestion is that instead of inviting people to develop a new mental model to deal with the scoping rules of Julia, we use their current mental model to help them learn the practices that should be followed in this language, using some motivating features that make the small changes attractive for users.