I’m not a Julia pro by any stretch of the imagination but I just found something that wasn’t obvious and blew my mind in a very small way, and I figure there are probably other little things like this that people are discovering. So I thought maybe it would be good to have a thread here to capture these.
I’m not sure exactly how to define a protip but I guess it’s something that:
Isn’t obvious and is interesting/useful to the poster
Might be obvious and/or not interesting or useful to others – no judging
Can be stated succinctly, ideally in just one sentence. Note that the sentence could be “There’s a really simple way to do X: <link to page that describes how in detail>”
May or may not be in the manual or other documentation – we all RTFM (right…?) but some of us forget things or fall asleep before getting to a cool nugget
Note that this is protips about Julia generally, not (only) tips for Julia Pro!
Anyhow, the thing I found today was:
There’s something similar to the output of history in bash for Julia, and IMO it’s even better that bash’s history in a few ways (and also serves a different purpose than reverse-i-search at the REPL). It’s found in $HOME/.julia/logs/repl_history.jl.
Really get comfortable with multiple dispatch and use it to its full advantage rather than simply reproducing what you would do in an OO language. This is arguably the one “steep learning curve” that Julia has when coming from most other languages, but the payoff is huge. Once you get fully acclimated to multiple dispatch (I’d say it took me about 18 months after many years of C++), the experience is much like that of true level, you’ll never ever want to go back. Consider doing a from-scratch implementation of Grassmann numbers as a tour-de-force of what multiple dispatch can do. The idea that it’s possible to implement something like that and have it mostly “just work” in packages which never even conceived of such a thing is, to my mind, simply spectacular. I don’t know of anything quite like it in any other language, except possibly the older multiple dispatch languages from which Julia drew inspiration.
Some of my other favorite Julia features which I think everyone should take full-advantage of:
Julia takes linear algebra deadly seriously. A*B is matrix multiplication, exp(A) is matrix exponentiation to name a few, this is so with every AbstractMatrix out there, not just Matrix. LinearAlgebra has lots of special matrix types enabling easy optimizations, make use of them. Julia does more than any other general-purpose language I know of to try to narrow the gap between abstract mathematical thinking and actual code, and it’s a thing of beauty.
Julia takes arrays deadly seriously. Make full use of broadcasting, it’s one of the language’s best features. Many of your functions needn’t be written for AbstractArray at all, but can simply be broadcasted without the need for any additional code. Make use of broadcasting even when you have some scalar arguments.
Related to the above, you don’t need to force every AbstractArray to be an Array a la numpy. The vast majority of the time AbstractArray should work fine. On the other side of the fence, don’t overspecialize your type signatures to use overly specific array types.
Not really sure whether these are “pro tips”, in fact, probably not, they are just things that I like to say.
Generic programs scale ideas into programs. Remember, Number types don’t have to really be a “number”, and AbstractArray types don’t need to be an “array” of contiguous values. Dual numbers, Measurements.jl, MultiScaleArrays.jl, etc. give all sorts of existing packages new functionality, and this is how you compose packages in a way that scales beyond what you may have planned for.
Can you please elaborate on this? Eg with a link to an example. I am aware of the head-tail pattern applied recursively to tuples, but did not encounter tuples in more complex examples.
It would take more time than I have right now to fully elaborate (this really needs a blog post), but the head/tail pattern is the foundation for this kind of programming pattern. The number of transformations you can do inferrably with tuples is quite amazing. One recent place I’ve used this is the Interpolations rewrite—there are a bunch of (regrettably undocumented) examples in https://github.com/JuliaMath/Interpolations.jl/blob/master/src/utils.jl. Here’s one example:
To me the most convincing demonstration is actual examples. I mentioned Grassmann numbers before, these are called “dual numbers” in ForwardDiff.jl (not really sure where the term “dual number” comes from, it would seem to imply that these numbers are in some sense “dual” to \mathbb{C}, but if that’s the case I don’t see how).
Also, check out some of the references on the expression problem, this is the problem that multiple dispatch was originally conceived to solve (there are lots of good resources linked to by the wiki page). Something to keep in mind when reading about the expression problem: in other languages, how likely would you be to dive back in to another package you are using to extend its functionality? Even if you did, would they want your modifications to live in that package? How easy is it to extend the functionality of other packages in Julia? Julia breaks down the development barrier between packages with a vengeance.
As far as learning goes, I think to some extent it is actually uncharted territory. Few languages work this way. Julia is probably the most widely used language that does. In my case at least, it just took experience.
One piece of advice I like to give is that I feel that the sometimes criticized naming scheme in Base was designed to be conducive to multiple dispatch. As you know, underscores are discouraged. This encourages you to write succinct function names for functions with lots of methods rather than lots of functions with few methods. Usually if you have a super descriptive function name, it is a sign that you should break it into multiple methods. As a simple example, Julia would never have a function like exp_matrix(::AbstractMatrix), it’s just exp(::AbstractMatrix). Maybe you would want such a function for internal use in a package if it is more specialized than the name indicates, but if you expose those you are probably doing something wrong.
Roughly speaking, every time you opt to add a function method rather than create a new function you’ve increased the amount of generic code that it is possible to write. This is true even in OO languages, Julia just expands this by not requiring those methods to belong to an object (or even a module, since you can extend methods outside the modules they were defined in).
Probably existing Julia code. Multiple dispatch per se exists in some less known languages (Common Lisp, Dylan), but as mentioned by @ExpandingMan, combining it with Julia’s powerful parametric type system opens up possibilities for which there is little precedent.
Take a look at the Selector module in Documenter. It uses multiple dispatch to allow external packages to interface with the Documenter rendering pipeline.
OT but I’ve wondered the same thing. However ForwardDiff can track several “dual” components at once, and these different ϵ, ϵ’ etc. commute with each other, right? Unlike multiple Grassmann generators.
Fully agree about multiple dispatch comments, I think getting used to the idea that your implementation may have 5 different steps, all just methods of the same function, was a great thing to learn.