One of the big advantages of Julia for scientific computing is Julia's novel type system. Historically, practitioners of scientific computing have been ignorant of type systems. Conversely, experts on type systems have been historically ignorant of scientific computing. The result has been that before Julia, the benefits of a type system have been available in only limited cases and not widely understood.
Along the way we'll learn elements of Julia's syntax that are not obvious to the newcomer but are really useful.
We hope by the end of this homework, the uses of all sorts of "dots" .
, ...
,:
,::
will be clear.
(One might refer to the Punctuation doc
which can be really helpful.) Dots in a language are great once you understand them as they use very little "ink" so they don't distract, but if they are mysterious, then the language just seems unreadable.
The dot .
is used for decimal points, field names, and broadcast.
The triple dots ...
is used for slurping and splatting. (Love those names.)
The :
is used for ranges, in indexing, and to create a symbol (offically known as a quoted expression).
The ::
is used for type annotation.
Jupyter Tip: Esc + A and Esc + B (insert cell above/below -- teach your fingers this and you'll thank your fingers)
Warning: Structs can't be redefined. You'll have to reload the kernel and re-execute.
v
of type Array{Int64,1}
which has at least two positive and two negative integers.# v = [ (Try with and without commas, which one is right?)
bitstring
command on each element of v and interpret with precision the bits that you see.# bitstring.(v) # Remember the `.` (dot), pronounced broadcast, applies the function to every element.
v
of type Array{Float64,1}
which has at least two positive and two negative values. Including a few that are not exactly mathematical integers.# v = [
#v = [
# bitstring.(v)
bitstring(Float16(1.0))
Let's create a function that takes an Int as input and returns a vector containing an Int64, Float64, Float16, and String.
function vector_of_types(n::Int) # Here :: says the input is defined for Int's
[Int64(n), Float64(n), Float16(n), string(n)]
end
vector_of_types(17)
Notice this has type Array{Any,1}
typeof(vector_of_types(17))
Let's now create a composite type
struct FourTypes
n::Int64
x::Float64
y::Float16
z::String
end
Create an f
of this type using a constructor.
f = FourTypes(17,17.0,Float16(17),"17")
f = FourTypes(17,17,17,17)
I love the splat operator. Here's an example.
f = FourTypes(vector_of_types(17)...)
dump(f)
typeof(f)
FourVectorTypes
with field vn
,vx
,vy
,vz
which contains respectively vectors of int64s, float64s, float16s, strings
## Test your type
# FourVectorTypes([1,2,3],[1.0,2,3],Float16.([1,2,3]),["1","2","3","4"])
<a href="https://docs.julialang.org/en/v1/base/base/#Base.sizeof-Tuple%7BType%7D" sizeof and tell us what you learn for some built-in types and composite types. Try a type with an Int64 and a Float32. Any surprise?
struct MyTypeAndMe{T}
n::T
end
MyTypeAndMe(17)
typeof(MyTypeAndMe(17))
typeof(MyTypeAndMe(17.0))
typeof(MyTypeAndMe(Float16(17)))
typeof(MyTypeAndMe("17"))
One can read the doc on parametric types, but warning: you may find it a little confusing. (I did!)
Maybe best to understand that NumAndType(x)
creates an object whose type is NumAndType(
typeof(x))
and whose "n field" is x
.
s = MyTypeAndMe(42)
s.n
dump(s)
typeof(s)
s = MyTypeAndMe(rand(5))
s.n
dump(s)
typeof(s)
In mathematics there is the concept of field extensions such as $\mathbb{Q}[\sqrt{2}]$ which means arithmetic operations with $a+b\sqrt{2}$ where $a$ an $b$ are rational. One can also talk about $\mathbb{Z}[\sqrt{2}]$ where one extends the integers allowing only plus, minus, and multiply perhaps. Let's make this general in Julia:
struct ExtendSqrt2{T}
a::T
b::T
end
Base.:show(io::IO, x::ExtendSqrt2) = print(io, "$(x.a)+$(x.b)√2")
ExtendSqrt2(3,4)
typeof(ExtendSqrt2(3,4))
ExtendSqrt2(3.5,4.1)
typeof(ExtendSqrt2(3.5,4.1))
ExtendSqrt2(1//3,1//2)
typeof(ExtendSqrt2(1//3,1//2))
Extend +,-,* by defining Base.:+ etc. Demonstrate that these work.
Create a matrix whose elements are ExtendSqrt2 and an ExtendSqrt2 consisting of two matrices. What are the two different types? What are the operations?
Base.:*(x::ExtendSqrt2,y::ExtendSqrt2)=ExtendSqrt2(x.a*y.a+2x.b*y.b,x.a*y.b+y.a*x.b)
Base.:+(x::ExtendSqrt2,y::ExtendSqrt2)=ExtendSqrt2(x.a+y.a,x.b+y.b)
[ExtendSqrt2(i,j) for i=1:3,j=1:3]^2
s = :abc
typeof(s)
MeAndMyType(:abc)
Today's scientific computing and machine learning needs computational graphs for automatic differentiation, optimization and so many more uses. See if this makes sense to you. We will get back to this later in the semester.
abstract type HW2 end # This creates an abstract type called HW2 so we don't mix things up
If you wish to read about Abstract Types . Don't worry if this doesn't fully make sense yet.
struct Plus{A, B} <: HW2
a::A
b::B
end
Base.:+(x::Symbol, y::Symbol) = Plus(x,y)
Base.:+(x::HW2, y::Symbol) = Plus(x,y)
:a + :b
:a + :b + :c
:a + :b + :c + :d
We are not asking you to anything here but to show you how one can begin building a full symbolic calculator with just a screenful of lines in Julia. By the end of the course you will learn how it all works.
abstract type Op end
struct Add{A, B} <: Op
a::A
b::B
end
struct Subtract{A, B} <: Op
a::A
b::B
end
struct Max{A, B} <: Op
a::A
b::B
end
struct Mul{A,B} <: Op
a::A
b::B
end
struct LazyVar <: Op
s :: Symbol
end
Base.:show(io::IO, format::MIME"text/html", x::LazyVar) = print(io,"<b>", x.s, "</b>")
macro var(v)
esc(:($v = $(LazyVar(v))))
end
function evaluate(x::Add; vals...)
evaluate(x.a; vals...) + evaluate(x.b; vals...)
end
Base.:+(x::Op, y::Op) = Add(x,y)
Base.:+(x::Op, y::Number) = Add(x,y)
Base.:+(x::Number, y::Op) = Add(x,y)
#sub
function evaluate(x::Subtract; vals...)
evaluate(x.a; vals...) - evaluate(x.b; vals...)
end
Base.:-(x::Op, y::Op) = Subtract(x,y)
#max
function evaluate(x::Max; vals...)
max(evaluate(x.a; vals...), evaluate(x.b; vals...))
end
Base.:max(x::Op, y::Op) = Max(x,y)
#mul
function evaluate(x::Mul; vals...)
evaluate(x.a; vals...) * evaluate(x.b; vals...)
end
Base.:*(x::Op, y::Op ) = Mul(x,y)
Base.:*(x::Number,y::Op) = Mul(x,y)
Base.:*(x::Op,y::Number) = Mul(x,y)
evaluate(x::LazyVar; vals...) = vals.data[x.s]
evaluate(x; vals...) = x
@var x
@var y
u = x + 3*y
v = max(u,10*y)
w = y*x*u
for t∈[u,v,w]
println(evaluate(t,x=5,y=4))
end