This IJulia notebook is a list of nice tweaks of the Julia language. Features should roughly be listed with ascending importance.
How does your favorite technical computing language compare to this?
When using variable names, you have the full range of special characters at your disposal.
α = 3.4
print(α)
pi
3.4
π = 3.1415926535897...
When multiplying variables with scalar values, computer code allows the same abbreviation that is used in common mathematical notation.
f = x -> 3x^3 + 8x^2 - 3x
f(3)
144
Multiple comparisons can be simultaneously checked in one line.
vals = rand(40)
intermediate = 0.2 .<= vals .<= 0.6
vals[intermediate]
18-element Array{Float64,1}: 0.535697 0.222741 0.59952 0.533287 0.572283 0.503853 0.502219 0.50637 0.478151 0.340583 0.250666 0.504783 0.383561 0.246129 0.22982 0.501394 0.458332 0.284484
To avoid cluttering your workspace, successive operations on an input can also be written as a pipe similar to linux shell commands.
a = rand(400)
# manually compute standard deviation of b
b = exp.(a)
mu = mean(b)
centralized = b - mu
std = mean(centralized.^2)
0.2414363593614808
# written with pipe
std = a |>
x -> exp.(x) |>
x -> x - mean(x) |>
x -> x.^2 |>
mean
0.2414363593614808
Variable values can easily be incorporated into a string.
fileName = "data.csv"
println("The values are stored in $fileName")
The values are stored in data.csv
a = [1; 2; 3]
println("The values of a are: $a")
The values of a are: [1, 2, 3]
Ternary operators are an abbreviation for if ... else ... end expressions. The expression before "?" is the condition expression, and the ternary operation evaluates the expression before the ":" if the condition is true, or the expression after the ":" if it is false.
kk = 4
if kk > 3
println("greater than 3")
else
println("smaller than 3")
end
greater than 3
kk > 3 ? println("greater than 3") : println("smaller than 3")
greater than 3
Iteration over all entries of a variable can be done without manual indexing.
a = [1; 2; 3]
for ii=1:length(a)
print(a[ii], ", ")
end
1, 2, 3,
for entry in a
print(entry, ", ")
end
1, 2, 3,
Values of a tuple or array can be simultaneously be assigned to individual variables.
a = rand(10, 2)
(nRows, nCol) = size(a)
nRows
10
(mu1, mu2) = mean(a, 1)
mu1
0.34985726892799174
mu2
0.5567945237304567
Comprehension is an easy way to create arrays where individual entries follow some structure.
a = [1:10...]
10-element Array{Int64,1}: 1 2 3 4 5 6 7 8 9 10
a = [ii for ii=1:10]
10-element Array{Int64,1}: 1 2 3 4 5 6 7 8 9 10
a = [exp(ii)+2 for ii=1:10]
10-element Array{Float64,1}: 4.71828 9.38906 22.0855 56.5982 150.413 405.429 1098.63 2982.96 8105.08 22028.5
a = [ii for ii=1:10, jj=1:10]
b = [jj for ii=1:10, jj=1:10]
a
10×10 Array{Int64,2}: 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10
b
10×10 Array{Int64,2}: 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10
using Base.Dates
dats = [Date(2012, 4, ii) for ii=1:10]
10-element Array{Date,1}: 2012-04-01 2012-04-02 2012-04-03 2012-04-04 2012-04-05 2012-04-06 2012-04-07 2012-04-08 2012-04-09 2012-04-10
whichYear = [year(dt) for dt in dats]
10-element Array{Int64,1}: 2012 2012 2012 2012 2012 2012 2012 2012 2012 2012
The syntax for indexing of variables makes use of with square brackets. This way, functions and variables can immediately be distinguished at first sight. Some languages - Matlab, for example - do not share this property.
a[2]
2
Functions can be defined anywhere in the file, and need not reside in a separate file. This allows easy and natural decomposition of large tasks into separate pieces.
function addTwo(x)
y = x + 2
return y
end
addTwo (generic function with 1 method)
addTwo(a[2])
4
Like in other languages, functions naturally extend to vector inputs.
addTwo(a)
10×10 Array{Int64,2}: 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12
Splicing function arguments allows seamlessly switching between functions that require separate arguments on the one hand, and functions that require individual arguments combined in one array.
function multipleArguments(a, b, c, d, e, f)
return a + b + c + d + e + f
end
multipleArguments (generic function with 1 method)
vals = (1, 2, 3, 4, 5, 6)
multipleArguments(vals...)
21
vals = 1:6
multipleArguments(vals...)
21
Function behaviour may vary depending on the type of the arguments. This way, multiple functions with equal name may co-exist.
function reciprocalValue(x::Int)
return 1/x
end
function reciprocalValue(x::String)
return uppercase(x)
end
function reciprocalValue(x)
println("Method only makes sense for numbers and strings")
end
reciprocalValue (generic function with 3 methods)
reciprocalValue(8)
0.125
reciprocalValue("hello")
"HELLO"
reciprocalValue(NaN)
Method only makes sense for numbers and strings
One can easily define highly customized own types. Through multiple dispatch, behaviour of common mathematical operators can be defined for any new type.
type simplexPoint
x
y
end
sp = simplexPoint(0.4, 0.6)
simplexPoint(0.4, 0.6)
function reciprocalValue(sp::simplexPoint)
return simplexPoint(sp.y, sp.x)
end
reciprocalValue(sp)
simplexPoint(0.6, 0.4)
Julia comes with quite powerful metaprogramming skills. This allows you to work with both the values that are stored in the variables and the names of variables and functions that were used in the call. This way, you can take some code, manipulate it, and only then you evaluate it.
One example is the @stoptime macro. Before the macro evaluates the input, it starts a stopwatch, and it displays the time that was required after the evalution.
macro stoptime(expr)
quote
hhhh = 3 # some line of nonsense to show variable scope
tic()
$(esc(expr))
toc()
end
end
@stoptime (macro with 1 method)
@stoptime repChar = inv(rand(1000, 1000))
repChar
elapsed time: 0.369956927 seconds
1000×1000 Array{Float64,2}: -0.0398966 -0.060931 -0.245225 … 0.0479709 0.197159 -0.0169669 -0.0729217 -0.0777518 -0.0291327 -0.00646732 -0.0427447 -0.069952 -0.157902 0.0539282 0.0536562 -0.0398194 0.00810824 0.154448 0.020877 -0.0541446 -0.0139438 -0.0443435 -0.22536 -0.0472286 0.0648014 -0.0366247 -0.0249919 -0.0247154 … 0.0538417 -0.00704784 0.0311694 -0.00977778 0.0567191 -0.0345471 0.0605255 0.0706019 -0.0053184 0.0726704 -0.0503869 -0.0327451 0.0936043 0.0196762 0.114289 -0.0848013 -0.0356401 0.0581979 0.076484 0.395625 -0.0369234 -0.0950004 0.0409752 -0.000710625 -0.324255 … 0.0241187 0.104905 0.0421804 0.073345 0.356782 0.00151367 -0.0381933 0.00295578 0.0794938 0.137912 0.0513759 -0.00323746 ⋮ ⋱ 0.0256882 -0.0510125 -0.180646 -0.0121656 0.0129868 0.00407903 0.0282648 0.169564 0.0176511 -0.0251555 0.0329774 0.0210937 0.104183 … -0.0411008 -0.0196085 -0.0506147 -0.0777601 -0.224726 0.0111449 -0.00382086 0.0478594 0.0132612 0.274135 -0.101028 -0.027347 0.0435744 0.128179 0.026532 0.0784626 0.0149802 0.0565033 0.0732664 0.336896 -0.0860393 -0.091944 -0.0317011 0.0235546 0.0265715 … -0.0428028 0.0703357 -0.00744253 -0.00950426 0.255367 -0.0329561 -0.111941 0.00804759 0.0416596 0.161445 0.00449368 -0.0650694 0.00235893 -0.020212 -0.320655 0.0433633 0.0519459 0.0354917 0.0744786 0.0283026 -0.0267 -0.0164742
Macros evaluate in a separate workspace.
try
hhhh
catch e
println("Type of error: $(typeof(e))")
println("Undefined variable: $(e.var)")
end
Type of error: UndefVarError Undefined variable: hhhh
Using the macroexpand function one can easily look at the complete code that gets evaluated by the macro.
macroexpand(:(@stoptime repChar = inv(rand(1000, 1000))))
quote # In[36], line 3: #15#hhhh = 3 # In[36], line 4: (Main.tic)() # In[36], line 5: repChar = inv(rand(1000, 1000)) # In[36], line 6: (Main.toc)() end
Of course, this stopwatch macro already exists in Julia. It is called @time.
Another example is the @test macro, which allows extremely convenient testing of code.
using Base.Test
@test 3 == (2+1)
Test Passed
As a macro also receives the actual variable names during its call, it can print out the actual call if the test fails.
Another example, though not the best style, is the following macro that returns the squared value of any given variable. The value will be stored in a variable that matches the name of the original input variable, but with "_squared" appended. Hence, his macro messes with the current workspace, which is generally NOT recommended.
In this example, however, we explicitly want the code to conduct changes to the workspace that are not directly induced through the expression that is handed over to the macro. The macro uses eval to create a new variable with ending "_squared" in the workspace.
macro squaredVariable(a)
println("Calculating the squared value of $a:")
newVariableName = Symbol(*(String(a), "_squared"))
eval(:($newVariableName = $a^2))
end
@squaredVariable (macro with 1 method)
k = 8
@squaredVariable k
Calculating the squared value of k:
64
k_squared
64
A crucial feature of the Julia language is that the syntax itself is just implemented in the language just like any other type (Int, Char, ...). Its type is Expr.
cmd = :(x = mean(rand(10)))
typeof(cmd)
Expr
As with any other type, you can access its fields, which are:
fieldnames(cmd)
3-element Array{Symbol,1}: :head :args :typ
Hence, you can find the operation in the :head field, and its arguments in the :args field.
cmd.head
:(=)
cmd.args
2-element Array{Any,1}: :x :(mean(rand(10)))
Using macros and other metaprogramming capabilities, some quite complicated applications can be implemented very concisely. As an example, we now want to implement a macro called bootstrap. For any given Julia function call that evaluates some statistics for some given data sample, the macro shall re-calculate the same statistics for a given number of times with bootstrapped data.
To make the steps a little bit more obvious, let's see step by step, how a given command can be decomposed into the necessary parts.
expr = :(mu = mean(x))
:(mu = mean(x))
At the top level, the command is an assignment.
expr.head
:(=)
The left hand of the assignment can be accessed as follows:
expr.args[1]
:mu
And the right hand is the complete function call:
expr.args[2]
:(mean(x))
Again, this can be decomposed into the function that is called,
expr.args[2].args[1]
:mean
and the name of the data variable that needs to be resampled:
expr.args[2].args[2]
:x
Hence, we now could isolate both the sample data and the function that calculates the required statistics. We can then apply the same function to bootstrapped data samples.
macro bootstrap(nTimes, expr)
quote
# get real value
$(esc(expr))
# get function to resample
func = $(expr.args[2].args[1])
# get data as first argument to function
data = $(expr.args[2].args[2])
nObs = length(data)
bootstrVals = Array{Any}($nTimes)
for ii=1:$nTimes
sampInd = rand(1:nObs, nObs)
samp = data[sampInd]
# apply function to sample
bootstrVals[ii] = func(samp)
end
res = bootstrVals
end
end
@bootstrap (macro with 1 method)
As we can see, the bootstrap macro works:
macroexpand(:(@bootstrap 1500000 mu = mean(x)))
quote # In[55], line 4: mu = mean(x) # In[55], line 7: #18#func = Main.mean # In[55], line 10: #19#data = Main.x # In[55], line 11: #20#nObs = (Main.length)(#19#data) # In[55], line 12: #21#bootstrVals = (Main.Array){Main.Any}(1500000) # In[55], line 13: for #22#ii = 1:1500000 # In[55], line 14: #23#sampInd = (Main.rand)(1:#20#nObs, #20#nObs) # In[55], line 15: #24#samp = #19#data[#23#sampInd] # In[55], line 18: #21#bootstrVals[#22#ii] = #18#func(#24#samp) end # In[55], line 20: #25#res = #21#bootstrVals end
x = rand(200)
muBstr = @bootstrap 150000 mu = mean(x)
mu
0.5250469301338273
muBstr
150000-element Array{Any,1}: 0.552315 0.556226 0.54129 0.528727 0.490689 0.507019 0.518092 0.528911 0.490504 0.501154 0.513485 0.5207 0.525462 ⋮ 0.518048 0.503648 0.529009 0.545387 0.515207 0.521471 0.536102 0.506838 0.512555 0.521913 0.526942 0.533516
Although the bootstrap macro only allows functions with only one argument, its reach can easily be extended through the use of anonymous functions.
varNineFive = x -> quantile(x, 0.95)
VaR_btstr = @bootstrap 150 VaR = varNineFive(x)
150-element Array{Any,1}: 0.958983 0.967951 0.951907 0.969697 0.972963 0.96102 0.955831 0.976543 0.952048 0.960836 0.969861 0.955831 0.964854 ⋮ 0.960836 0.968441 0.960836 0.977267 0.976956 0.939361 0.967972 0.955452 0.955452 0.972963 0.968441 0.95882
versioninfo()
Julia Version 0.6.0 Commit 9036443 (2017-06-19 13:05 UTC) Platform Info: OS: Linux (x86_64-pc-linux-gnu) CPU: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz WORD_SIZE: 64 BLAS: libopenblas (USE64BITINT DYNAMIC_ARCH NO_AFFINITY Haswell) LAPACK: libopenblas64_ LIBM: libopenlibm LLVM: libLLVM-3.9.1 (ORCJIT, skylake)
Pkg.status()
172 required packages: - AbstractFFTs 0.2.0 - Atom 0.6.1 - AutoGrad 0.0.7 - AutoHashEquals 0.1.1 - AxisAlgorithms 0.1.6 - AxisArrays 0.1.4 - BenchmarkTools 0.0.8 - Blink 0.5.3 - Blosc 0.3.0 - BufferedStreams 0.3.3 - BusinessDays 0.7.1 - CSV 0.1.4 - Calculus 0.2.2 - CatIndices 0.0.2 - CategoricalArrays 0.1.6 - Clustering 0.8.0 - CodeTools 0.4.6 - Codecs 0.3.0 - ColorTypes 0.5.2 - ColorVectorSpace 0.4.4 - Colors 0.7.4 - Combinatorics 0.4.1 - Compat 0.28.0 - Compose 0.5.3 - ComputationalResources 0.0.2 - Conda 0.5.3 - Contour 0.3.0 - Convex 0.5.0 - CoordinateTransformations 0.4.1 - CoupledFields 0.0.1 - CustomUnitRanges 0.0.4 - DBAPI 0.1.0 - DSP 0.3.2 - Dagger 0.2.0 - DataArrays 0.6.2 - DataFrames 0.10.0 - DataStreams 0.1.3 - DataStructures 0.6.0 - DecFP 0.3.0 - DecisionTree 0.6.1 - DiffBase 0.2.0 - Distances 0.4.1 - DistributedArrays 0.4.0 - Distributions 0.14.2 - DualNumbers 0.3.0 - FFTViews 0.0.2 - FFTW 0.0.3 - FileIO 0.5.1 - FixedPointNumbers 0.3.9 - Formatting 0.2.1 - ForwardDiff 0.4.2 - GLM 0.7.0 - GR 0.23.0 - GZip 0.3.0 - Gadfly 0.6.3 - Glob 1.1.1 - Graphics 0.2.0 - HDF5 0.8.2 - HTTPClient 0.2.1 - Hexagons 0.1.0 - Hiccup 0.1.1 - HttpCommon 0.2.7 - HttpParser 0.3.0 - HttpServer 0.2.0 - HypothesisTests 0.5.1 - IJulia 1.5.1 - IdentityRanges 0.0.1 - ImageAxes 0.3.1 - ImageCore 0.4.0 - ImageFiltering 0.1.4 - ImageMetadata 0.2.3 - ImageTransformations 0.3.1 - Images 0.11.0 - IndexedTables 0.2.1 - IndirectArrays 0.1.1 - Interact 0.4.5 - Interpolations 0.6.2 - IntervalSets 0.1.1 - IterTools 0.1.0 - Iterators 0.3.1 - JDBC 0.2.0 - JLD 0.6.11 - JSON 0.13.0 - JavaCall 0.5.1 - JuMP 0.17.1 - JuliaWebAPI 0.3.1 - Juno 0.3.0 - KernelDensity 0.3.2 - Knet 0.8.3 - LNR 0.0.2 - LaTeXStrings 0.2.1 - Lazy 0.11.7 - LegacyStrings 0.2.2 - Libz 0.2.4 - LightGraphs 0.9.4 - LightXML 0.5.0 - LineSearches 0.1.5 - Loess 0.3.0 - Logging 0.3.1 - MLBase 0.7.0 - MNIST 0.0.2 - MacroTools 0.3.7 - MappedArrays 0.0.7 - MathProgBase 0.6.4 - MbedTLS 0.4.5 - Measures 0.1.0 - Media 0.3.0 - Mustache 0.1.4 - Mux 0.2.3 - NaNMath 0.2.6 - NamedArrays 0.6.1 - NamedTuples 4.0.0 - NearestNeighbors 0.3.0 - Nettle 0.3.0 - NullableArrays 0.1.1 - ODBC 0.5.2 - OffsetArrays 0.3.0 - Optim 0.7.8 - PDMats 0.7.0 - PaddedViews 0.1.0 - Parameters 0.7.2 - ParserCombinator 1.7.11 - PlotRecipes 0.2.0 - PlotlyJS 0.6.4 - Plots 0.12.3+ master - Polynomials 0.1.5 - PooledArrays 0.1.1 - PositiveFactorizations 0.0.4 - Primes 0.1.3 - ProtoBuf 0.4.0 - PyCall 1.14.0 - PyPlot 2.3.2 - QuadGK 0.1.2 - QuantEcon 0.12.1 - Query 0.6.0 - RCall 0.7.3 - RDatasets 0.2.0 - RangeArrays 0.2.0 - Ratios 0.1.0 - Reactive 0.5.2 - Reexport 0.0.3 - Requests 0.5.0 - Requires 0.4.3 - ReverseDiffSparse 0.7.3 - Rmath 0.1.7 - Roots 0.4.0 - Rotations 0.5.0 - Rsvg 0.1.0 - SCS 0.3.3 - SHA 0.3.3 - SIUnits 0.1.0 - ScikitLearnBase 0.3.0 - ShowItLikeYouBuildIt 0.0.1 - Showoff 0.1.1 - SimpleTraits 0.5.0 - SortingAlgorithms 0.1.1 - SpecialFunctions 0.2.0 - StatPlots 0.4.2 - StaticArrays 0.6.1 - StatsBase 0.17.0 - StatsFuns 0.5.0 - TexExtensions 0.1.0 - TextParse 0.1.6 - TiledIteration 0.0.2 - TimeSeries 0.10.0 - Tokenize 0.1.8 - URIParser 0.1.8 - UnicodePlots 0.2.5 - WeakRefStrings 0.2.0 - WebSockets 0.2.3 - WoodburyMatrices 0.2.2 - ZMQ 0.4.3 17 additional packages: - BaseTestNext 0.2.2 - BinDeps 0.6.0 - Cairo 0.3.1 - DataValues 0.2.0 - DocStringExtensions 0.4.0 - Documenter 0.11.2 - DynAssMgmt 0.0.0- master (unregistered) - EconDatasets 0.0.2+ master - GeometryTypes 0.4.2 - Gtk 0.13.0 - IterableTables 0.4.2 - LibCURL 0.2.2 - NetworkLayout 0.1.1 - PlotThemes 0.1.4 - PlotUtils 0.4.3 - RData 0.1.0 - RecipesBase 0.2.2
scriptEndIsReached = true
true