Feedback on an "Intro to Julia" guide for a university course

Hi all,

I’m getting ready to teach a new course in the fall – a 400-level computational physics class – and I’m on the verge of making the (exciting? daunting? rash?) decision to use Julia as the primary language for it despite being quite new to the Julia ecosystem myself. I’ve been working on a set of introductory lecture notes to get everyone up to speed and on the same page with the language, aimed at students whose programming background might be quite sophisticated, but might also be as little as a single semester using Python.

I think there can be some unique advantages to developing teaching materials while one is still close to the initial learning curve (and certainly I’ve been curious about Julia for some time now, and I thought this could be a fantastic excuse to dive into it). However, I also know that this also means that there may be important aspects I’ve overlooked, non-idiomatic approaches in my examples, or clearer ways to explain core Julia concepts. I’ve put together a first draft, and you can access the link from this page: PHYS 436: Advanced computational physics | Sussman Lab.

Thanks for your time – I’ve already learned a tremendous amount just lurking around the discourse, and I’d be very grateful to call on your collective input here. I’m especially interested in your thoughts on the progression of topics, the depth of coverage, and the clarity of my explanations. But really: all forms of feedback and constructive criticism would be most welcome; I’m hoping that this turns into a valuable learning resource!

Best,
Daniel

(edited to add the link to the guide)

18 Likes

Good, This is nice decision. You can suggest your students these links as additional resources.

2 Likes

What does it mean?

Direct links: PHYS 436: Advanced computational physics, draft chapter

First, thanks for adding the direct link when I wasn’t able to!
Also, sorry for not providing the context: “400-level…” means that most students taking it will be 3rd or 4th year university students, and in this case the students will mostly be majoring in physics. The prerequisites for the class, though, are only an “intro to computational methods” kind of course (usually taught in python) and a “mathematical methods in the physical sciences” kind of course.

1 Like

There was this recent post about support for Julia in Google Colab (Julia in Colab). I wonder if a small blurb about Julia in Colab would be beneficial.

2 Likes

Definitely! I mention in it the first chapter (A.4 “Notebooks and IDEs” – while I don’t use notebooks much myself, I think having it work out of the box in colab will be great for students!

1 Like

That’s what I get for glancing over the text and not noticing stuff about Colab.

Also, I don’t know if you have it in the main textbook, a word of caution about making your own macros might be beneficial to your students new to programming in Julia. This thought came to mind when you mentioned macros

1 Like

That’s what I get for glancing over the text….

No worries — I appreciate that you took the time to take even a quick look!

Also, I don’t know if you have it in the main textbook, a word of caution about making your own macros…

Totally agree. At the moment I’m at ~50 pages for this intro material, and I’ve so far left this particular topic at “macros exist, and we’ll encounter a few that are extremely useful even before we start understanding how they work or how to write them.” My thought is that I’ll see if there’s a case in the main material of the class (as opposed to this primer on the language) where writing a macro would be a natural solution to part of the coding problem at hand; if so I’ll consider writing more material in this guide on that topic.

Overall I think this is a really good intro to Julia. It doesn’t fall into the trap of trying to entirely or nearly entirely explain each aspect at once (the Manual does, but to be fair, that’s what Manuals are for), and it gets people doing math throughout the whole process instead of settling for contrived demonstrations (readline-println is a bane of programming 101). You don’t have the time to explain all the fundamentals (like expression parsing), but they know about environments, versioning, and some useful packages, which is impressive practical knowledge in such short time. It also doesn’t fall into the trap of explaining all the “easier” things first. Initially I found it bold that you teach environments and a basic package before you do multimethods, but while I personally found multimethods easier to grasp, it actually makes sense to me that most people may benefit learning most of the syllabus with single-method functions. Better to see the features Julia shares with more languages before the ones it shares with few.

Expand for corrections (marked !!) and some suggestions, apologies for the wordy ones.
  • Preface: I’d prefer if you actually sold Julia less. Your points on why it was chosen all made sense, but it’s only one choice out of several great ones with their own reasons. If anything, you could just point out that it’s more beginner-scientist-friendly than the C/C++ you use day-to-day.
  • Major points for pointing out environments and reproducibility first thing, even though you’re not immediately jumping into it for beginners. Bloated global environments are such an easy trap to fall into that it’s better to be aware sooner than later. However, you don’t seem to specify versions for Julia or added packages, and I’d say you made a great argument for why you should. I should add that v1.12 will soon introduce constant variables to the world-age/invalidation deal that methods go through, so it’ll be better for you and your students to know what era of Julia they’re getting into. Wouldn’t want some early birds looking at your website now be confused down the line by how their peers are Revising structs.
  • !! B.2.3 Your distinction of immutable and mutable values wrt reassignment is incorrect. If you reassign a value to a local variable, distinct outer variables are unaffected regardless of the involved values’ mutability. a[1] = pi is an assignment, but it’s not reassigning to the variable, it’s reassigning to an element at index 1 of its value. That can’t happen for semantically immutable values, of course (to clarify, immutable composite types may wrap mutable types and thus support mutation at an element or property, just not a direct field, but you would probably omit that nuance until much later when you talk about defining types).
  • I see you mention “hard local” scope but don’t really explain the hard/soft deal. I think it’s good you’re dealing with things in hard-scoped functions first, but it’s not worth mentioning its hardness until you explain it. You should get people used to how local scopes reassign outer local scope’s variables by default (sharing is caring) but not globals, explaining that it’s to avoid surprises across the possibly multiple files of a global scope. Then, soft scope can be explained as a local-like convenience for some block expressions at top-level in interactive contexts where you don’t contend with multiple source files; a result += for-loop is probably the better example. Probably should also show that such blocks elsewhere (not just source files, you can also use eval or include_string to distance the expression from the interactive context) would need local/global disambiguation to suppress warnings. I’m annoyed that this is even a thing to explain, but despite your due diligence to put things in functions, people WILL try to run scripts without functions very quickly. I’d say you can put off reassigning global variables in top-level soft scopes until the end of B; it’d be strange to explain soft scope behavior when only 1 such block has been explained, but just listing the few with this headache wouldn’t hurt (“we’ll see these later”).
  • !! D.4 “Functions should consistently return values of the same type” is misleadingly ambiguous, try “methods should return a consistent type depending on the input types”.
  • E.4 I think the multiple dispatch section can benefit from showing another function with a call that dispatches to the multiple methods of elasticCollision, like a multiple argument version of Python duck-typing examples.
  • You could also teach that with type stability, the compiler can identify types and perform dispatches at compile-time, removing the overhead from runtime as a routine source of performance optimization. Then help them recall that you suggested methods don’t be ambiguous about return types earlier, and explain that successive calls on values with uncertain types at compile-time quickly snowballs into the compiler giving up and doing runtime dispatches.
4 Likes

Julia has been the primary driver of the (mandatory) computational physics course at the University of Cologne for more than 6 years. See e.g. Computational Condensed Matter Physics | Simon Trebst (in German)

8 Likes

Julia has been the primary driver… [at] Cologne…

Thanks for the link! Just to be completely clear if the way I phrased it above was ambiguous – any potential trepidation about having the class be based on Julia was not meant to reflect any uncertainty about whether it was fit for the purpose, but whether I was. That is: I feel like I could have taught this class in C++ in my sleep, whereas I’m both excited and a bit anxious at the prospect of being basically just a few months ahead of the students in my knowledge of Julia.

(By the way: thanks for a lot of the workshop material you’ve put together over the last several years! I didn’t touch on HPC in these notes, but I learned a lot by working through your UCL24 notebooks)

2 Likes

@Benny, this is fantastic, thanks! I appreciate the general kind words, and I especially appreciate the detailed feedback and corrections! I’m compiling all of the feedback I get and will incorporate it soon; if you happen to notice anything else I’ll be sure to add it to the list!

Expand for brief responses to some of your comments. Also, thanks for implicitly teaching me about BBCode annotations!
  • Environments and packages before multiple dispatch: I’m glad you see the perspective I was going for, and yes – I thought it would be easier for them to see the overlaps Julia has with other languages first rather than starting with some of the most unique features!
  • Versions for Julia and added packages – great points, I’ll add this! I guess it’s likely that 1.12 will be out by the time the fall semester starts, and I’ll have to decide whether to mention some of the specific features that get added in it.
  • mutable and immutable error in the notes: thanks for catching this! Crucial correction for me to fix.
  • Scope rules: I think you’re right that I should talk in more depth about scopes (and you have some very helpful suggestions for how to do so, thanks!); I need to spend some time thinking about where the most natural place to do so is.
  • D.4 correction: again, thanks for the enhanced precision, here!
  • Multiple dispatch enhancement: good call – I am currently planning on having a lot of the code to make the EDMD section run be part of the students’ problems to work on, but I agree that for the purpose of highlight the what and why of multiple dispatch another such example would be beneficial.
1 Like

There are only two hard things in Computer Science: cache invalidation and naming things.

Phil Karlton

Your students probably won’t have to deal with the former, but give a nod to namespace, especially if they get into writing their own modules.

I may be missing something, but it looks like everything except Appendix D is covered by the manual.

I would consider the trade-offs of maintaining that vs just referring to selected chapters / sections of the manual. This would have the extra advantage of students already being introduced to one great source of documentation that they can grow into later on.

The animal type hiearchy in E.3.2 reminds me of C++ textbook examples. I think that building abstract type hierarchies like this is pretty useless in Julia unless you want to use it for dispatch. I would just remove it and mention abstract types. Similarly for the type tree graph in B.1.

As for the WIP section on scope in E.2.1, Julia’s scope is rather intuitive but the exact rules are complicated. When I teach Julia, I just tell students about good practices so that you don’t ever have to think about corner cases. Again, I would just refer to part of that chapter in the manual.

Appendix D is great, picking a plotting package for students saves a lot of confusion.

3 Likes

I don’t agree with this. The manual tries to be both a reference and a tutorial, and it’s not great at either of those things. It just keeps accruing more stuff, without a consistent style (I’m saying this having learned Julia mostly by reading the whole Julia 1.5 manual).

There is an advantage to having a more self contained resource. In that sense, writing the “intro to Julia” first might not be as effective as writing the other chapters first, making note of the prerequisite knowledge, and then writing the introductory chapter to fill in those prerequisites.

4 Likes

First, thanks for your feedback on this – I appreciate your perspective, and I know you’ve done radically more both with respect to Julia itself and the teaching of it than I have! A few comments:

I may be missing something… consider the trade-offs of maintaining that vs just referring to selected chapters of the manual.

I agree that – excluding some things like choosing a specific plotting package, having a loose theme connecting the examples we work through, and a few general remarks at the end of the chapters – the information content is pretty much a strict subset of what’s in the manual. I also agree that there is a non-negligible cost to not just pointing directly to the manual for most things (and one might reasonably complain that with the big green “this guide is not a substitute for reading the manual” disclaimer at the top of page 1 that I’m trying to have it both ways).

On the other hand, I don’t really believe that the manual is the perfect pedagogical structuring of information for most students who are new to the language, and I think that’s a tension which almost always exists for something striving to be both authoritative and comprehensive. Certainly when I’ve been trying to learn the language recently I’ve always had the manual open on one tile of my screen, but frequently while simultaneously reading through a different tutorial, or watching one of the playlists on the julialang channel, or… So, yes, there is a cost to maintaining a guide vs referring to sections of the manual, but I also think there can be a real benefit from having a somewhat expanded introductory guide that tries to synthesize a lot of that information. (I hope I don’t need to say this, but to be clear: obviously I don’t think what I wrote is “a perfect pedagogical structuring” either! But I do hope I can eventually get it into a shape where it’s a positive and non-zero contribution.)

The animal type hierarchy reminds me of C++ textbook examples…

I guess my own education is showing through too much (and so much for the BigInt joke)! But this is a good point – the text illustrates that you can build that kind of abstract type hierarchy, but it might also accidentally emphasize that you should think of organizing your code this way (even if you aren’t going to use it for dispatch).
Similarly, perhaps the type tree in B.1 reinforces the idea of “think of everything in terms of its “is-a” classification” rather than “think about what common interfaces we want to define for dispatch”. I’ll think about how to better work through these concepts.

When I teach Julia, I just tell students about good practices so that you don’t ever have to think about corner cases.

I agree that, given the stage the students will be at by the time they get to the section on scope in E.2.1, just a few words plus a pointer to the manual might be all that I need to do. Out of (legitimate) curiosity – does the quoted advice above work? In my limited experience (and not in the context of this specific example, of course), students will (a) know about good practices but (b) get themselves into trouble anyway by selectively ignoring them.

Appendix D is great…

Thanks for ending on a positive note, and more importantly thanks for taking the time to look through what I did and share your perspective!

1 Like

The intro example on page 5 gives the Float64 representation of pi, which is only accurate to 16 digits:

julia> using Printf

julia> @printf("Hello, pi!\npi=%.40f",pi)
pi=3.1415926535897931159979634685441851615906

julia> big(pi)
3.141592653589793238462643383279502884197169399375105820974944592307816406286198

julia> big(pi) - 1.0pi
1.224646799147353177226065932275001058209749445923078164062861980294536250318213e-16

It could be tricky to introduce arbitrary-precision arithmetic at the very start of the tutorial, but your readership might include other pi-digit-memorizing nerds who also notice the discrepancy. You could do something like this:

julia> @printf("Hello, pi!\npi=%f", pi)
Hello, pi!
pi=3.141593

julia> @printf("π=3 might be good enough for physicists,\nbut it's actually closer to to %.40f", big(pi))
π=3 might be good enough for physicists,
but it's actually closer to to 3.1415926535897932384626433832795028841972
2 Likes

Nice catch, the specifier would’ve forced that conversion. Looking at floating point precision would also be informative:

julia> print("pi=", big(pi)) # wrong at trailing 198 due to default precision
pi=3.141592653589793238462643383279502884197169399375105820974944592307816406286198
julia> @printf("Hello, pi!\npi=%.78f", pi) # auto Float64, 16 correct digits
Hello, pi!
pi=3.141592653589793115997963468544185161590576171875000000000000000000000000000000
julia> @printf("Hello, pi!\npi=%.78f", nextfloat(Float64(pi))) # also wrong at 16 digits
Hello, pi!
pi=3.141592653589793560087173318606801331043243408203125000000000000000000000000000
julia> @printf("Hello, pi!\npi=%.78f", eps(Float64(pi))) # 16 leading zeros
Hello, pi!
pi=0.000000000000000444089209850062616169452667236328125000000000000000000000000000
julia>

But I think printing 10 digits is preferable to explaining the abstract representation of an exact pi by a dedicated Irrational{:π} type, floating point precision, or automatic conversions and promotions at that stage. That could be a separate section or two if there’s time because students who have a single semester in any language probably won’t know. Then again, there’s plenty of even cross-language knowledge that people should know but probably can’t be crammed into one semester, a good teacher needs to pick their battles.

1 Like

I like how you write! Here’s some criticism:

  • B.1 Variables and types

    • Criticism of the second paragraph, which talks about “type annotations”:

      • “Type annotations” are not a single feature. There are several different language features here, even though they share the :: syntax:

        • Putting a constraint on the types of arguments a method will accept. May be useful for type safety or for multiple dispatch, but does not constrain the allowed type of the variable. That is, constraining a method argument with x::Int does not prevent assigning a Float64 to x in the method body, like x = 0.3.

        • Declaring the type of a variable (either local or global): this ensures the type of the variable is as declared on each assignment, with a typeassert, but only after first calling convert on the RHS of the assignment.

        • A simple typeassert on an expression: just throws if the expression is not of the right type. Example: (1 + 2)::Int.

      • In fact I’d recommend not using the phrase “type annotation” to prevent confusion. Just describe each specific feature, if necessary. And don’t describe more than one in the same paragraph, that’s confusing.

      • Please, do not imply in your text that type annotations are desirable for performance. Sometimes it’s possible to apply a type annotation (in either meaning) to improve performance, but this is not common at all. As-is, your text seems to encourage your students to use type annotations, which is the opposite of what you should be doing. Anyway, when performance is a relevant topic, it’s usually the best to just link to the Performance tips page in Julia’s Manual, or to the relevant subsection: Performance Tips · The Julia Language

      • Perhaps don’t even mention anything about type assertions or variable type declaration in your text? It’s an advanced topic, while your text is merely an introduction to Julia. Certainly don’t start with type annotations.

    • Figure B.1: A small subset of Julia’s type tree.

      • typos:

        • AbstractReal should be Real

        • AbstractInt should be Integer

        • AbstractSigned should be Signed

        • AbstractUnsigned should be Unsigned

3 Likes

You make a valid point, and I would not recommend the manual as a starting point for someone new to programming. But since it is a 400-level class, I am assuming that the students can cope. In any case, you are the person who knows them best.

Nevertheless, you may find that condensing the information in the manual is surprisingly difficult. The manual is complex in a lot of places because Julia itself is.

For scoping specifically, I think it does. My advice is that almost everything should be in functions, and then I don’t have to think about scoping much. Similarly, it is advantageous to put everything in modules early on for more than 50–100 LOC, and use Revise.jl.

OTOH, overwhelming the students with workflow practices too early can be counterproductive. It really depends on what you are doing.

Do you have a TA? I think a lot will depend on how familiar the TA is with Julia, so that he/she can guide students through the minor problems they may encounter.

2 Likes