Julia: Tutorial & Code-Collection¶
Source: https://github.com/markomlikota/CodingSoftware
MIT License, © Marko Mlikota, https://markomlikota.github.io
Coding Advice¶
General Advice¶
Before you start coding, be clear on what you want to achieve and how to go about it (roughly).
Divide program into (many small) functions. This is easier to reuse, to test, and it (often) runs faster.
For standard problems (e.g. interpolation, kernel density estimation, etc.), before you do it yourself, see whether there is a library that does it.
For bigger projects, use Git to keep track of version changes.
If you find yourself creating a newer version of some file, leave the old version's name as-is and attach a version-suffix (e.g. _v3) to the filename of the new version. This way there is a unique filename for every new version, and old versions can just be discarded in an "OldFiles"-folder and re-taken if needed without creating naming problems (which would arise if you deleted the version extension for the currently best version).
Think about optimization only once the code runs. And ask yourself whether an optimization (making code faster, graphs prettier, etc.) is really needed.
Code Structure & Naming of Objects¶
Clearly structure your code using comments, lines and spaces to create sections, subsections, etc. This will make it much more readable (for your future self and for others).
At the top of your code, include a header/preamble with your name, contact information (email/website), the purpose of the code (title and a short description)
Start your code with some preliminary sections where you specify options, load all packages necessary to run the script, define paths, and load external code.
Adopt a naming convention, e.g.
use the so-called lowerCamelCase
start binary variables with "is" or "include" or other verb (e.g. isUtilityPositive or includeLimits)
start functions with f (e.g. fCombineStrings)
start object-names with v, a, m, i for vector, array, matrix, iterator, string (and combine, if necessary; e.g. vsVars is vector of strings
See https://github.com/markomlikota/CodingSoftware for template .jl- and .jmd-files.
Julia-Specific Advice¶
# As discussed above, Julia is a just-in-time-compiler;
# the first time a code is run, it optimizes its computations
# so that the code is executed faster in subsequent calls
# (this holds for codes that are literally executed several times,
# but also for loops and similarly "repetitive" codes.)
# You can help Julia optimize the code in several ways.
# Constants and Global vs Local Variables
# Declare variables whose value will not change
# during the current Julia-session as constants:
# e.g.
const δ = 5
# One can, in principle, redefine a constant,
# but you are advised not to do so:
δ = 3
WARNING: redefinition of constant Main.δ. This may fail, cause incorrect answers, or produce other errors.
3
# Type-Stability
# Avoid changing the type of a variable;
# e.g. given
x = 1.5
# redefine x as
x = 2.0
# rather than
x = 2
# Let your functions' output be always of the same type,
# regardless of the type of the input supplied to them.
# e.g. write
fMyFun(x) = 2.0 * x
# rather than
fMyFun(x) = 2 * x
# (Former always returns float,
# while latter returns output of same type as x.)
# Ideally, also let your functions always take inputs of the same type,
# though this is, apparently, less important.
# e.g. write
fMyFun(x::Float64) = 2.0 * x
# and call
fMyFun(2.0)
# or
fMyFun(Float64(2))
# rather than letting, as above, fMyFun(x) take x of any type.
4.0
# Pre-allocate outputs:
# e.g. instead of creating an object (e.g. a matrix) every time anew (say in a loop),
# create it once (e.g. before the loop) and then just fill it up with different values.
# This optimizes your code in general,
# and even more so if the object stays of the same, specific type.
# Use @. in functions that perform vectorized operations on long vectors:
# e.g. this
fMyFun1(x) = @. 3x^2 + 4x^3 + 4
# is faster than this
fMyFun2(x) = 3x.^2 + 4x.^3 .+ 4
vx = rand(10000)
@time fMyFun1(vx)
@time fMyFun2(vx)
0.128975 seconds (386.11 k allocations: 26.497 MiB, 99.96% compilation time) 0.274181 seconds (350.97 k allocations: 24.391 MiB, 13.82% gc time, 99.97% compilation time)
10000-element Vector{Float64}: 10.496584532958767 6.439532126827355 4.001113742676998 5.0568304912004844 10.886869913279174 5.9614361363542185 4.516996449742614 10.968299664943107 4.553604952233805 9.783594719420094 4.803344608172253 7.4565479830390675 4.28177830647821 ⋮ 8.57113519474725 7.658954961238093 6.285528900523082 4.459668564411588 8.456898753085628 4.352197745895103 4.1973337794137935 4.785647651358424 9.103117708280328 4.518020819202778 6.9489950634368 4.119407764087473
# Consider using @views when accessing parts of a vector or a matrix.
# When you use e.g. mA[1:50,41:70], this creates a copy of this "slice" of mA.
# This is all right if you use this slice in several operations.
# However, if you do only a few operations,
# it is better to write "@views" in front of these operations,
# which avoids storing a copy of the slice.
# e.g. for
mA = rand(100,100)
# write
@views mA[1:50,41:70]
# instead of
mA[1:50,41:70]
# compare time needed for these two methods of accessing:
@time @views mA[1:50,41:70]
@time mA[1:50,41:70];
# e.g. when using the slice in a function:
@time @views sum(mA[1:50,41:70])
@time sum(mA[1:50,41:70]);
0.000006 seconds (1 allocation: 64 bytes) 0.000015 seconds (1 allocation: 11.875 KiB) 0.051160 seconds (92.54 k allocations: 6.315 MiB, 13.65% gc time, 99.88% compilation time) 0.041072 seconds (28.68 k allocations: 1.965 MiB, 99.89% compilation time)