Julia is developed as an easy to use scientific language. It is a high-level, multi-paradigm language that Prides it's self for being efficient to both code and during execution.

- mulit dispatch
- Dynamic typing
- Just-In-Time (JIT) complier
- Distributed parallel computation

Julia uses coding styles that are familiar to many programmers who have experince with other languages. A lot of julia code could be read by programmers not even familiar with julia. It seemlessly adds some of the stragies of a functional programming language in a way that is easy to understand.

In [1]:

```
x = 1
```

Out[1]:

In [2]:

```
typeof(x)
```

Out[2]:

In [3]:

```
type classmates ##mutable Struct
name::String
ID::Int
end
```

In [4]:

```
b = push!([], classmates("Bob.", 95))
```

Out[4]:

In [5]:

```
push!(b, classmates("Sarah", 90))
push!(b, classmates("Alex", 100))
```

Out[5]:

Julia is able to make closures around functions automatically.

In [54]:

```
function makecounter(in::Int)
c = in
println("made counter")
return function()
c = c +1
return c
end
end
```

Out[54]:

In [55]:

```
count1 = makecounter(0)
```

Out[55]:

In [58]:

```
count1()
```

Out[58]:

In [7]:

```
function volume(s::Real) #volume of a cube
return s * s * s
end
```

Out[7]:

In [8]:

```
function volume{T <: Real}(l::T, w::T, h::T) #Volume of a box
return l * w * w * h
end
```

Out[8]:

In [9]:

```
volume(2, 3, 4)
```

Out[9]:

In [61]:

```
type weirdnum
num::Int32
end
```

In [62]:

```
foo = weirdnum(2)
```

Out[62]:

In [63]:

```
faa = weirdnum(3)
```

Out[63]:

In [64]:

```
foo + faa
```

In [65]:

```
import Base.+
function +(a::weirdnum, b::weirdnum)
return weirdnum(a.num + b.num)
end
```

Out[65]:

In [67]:

```
foo + faa
```

Out[67]:

function generic(args) return args

In [72]:

```
function generic(a)
println(a)
end
```

Out[72]:

In [71]:

```
function generic(num::Number)
println(num^2)
end
```

Out[71]:

In [76]:

```
function generic(a, Varargs...)
println(a, " followed by ", Varargs, "as a tuple")
end
```

Out[76]:

In [74]:

```
function generic(b::Array{Any,1})
println("::",b,"::")
end
```

Out[74]:

In [77]:

```
generic("Hello World")
generic(2)
generic("first call", 2, 3, 4, 5)
generic([1, 2, 3])
```

Julia has fairly expansive array handling capabilities for the scientific uses that it has. A cleaner syntax for Multidimensionl arrays means that it can be easily used for the linear algebra that is required in some fields. While the syntax between the different demensions of arrays are slightly different both are very nice seperately. you just have to get used to changing the syntax.

Julia's multidemensional arrays are limited to being a consistent width and height across different columns and rows. For that you have to go back to older languages arrays of arrays.

In [24]:

```
one_D_array = ["a","b","c"]
```

Out[24]:

In [25]:

```
two_D_array = ["a" "b"; "c" "d"]
```

Out[25]:

In [26]:

```
typeof(one_D_array)
```

Out[26]:

In [27]:

```
typeof(two_D_array)
```

Out[27]:

In [78]:

```
one_D_array[1]
```

In [29]:

```
for i in 1:length(one_D_array)
print(one_D_array[i], " ")
end
```

In [30]:

```
for i in 1:length(two_D_array)
print(two_D_array[i], " ")
end
```

In [31]:

```
two_D_array[1,2]
```

Out[31]:

In [32]:

```
identity = eye(3)
```

Out[32]:

In [33]:

```
one = ones(2,3)
```

Out[33]:

In [34]:

```
zero = zeros(3,2)
```

Out[34]:

Existing arrays can have different parts selected by using square brackets. In place of the numbers within these arrays you can use ranges (format of 2:3) in order to select a specific subset of the array or simply an : in order to select the whole dimension.

Additionally Julia has a lot of built in functions for manipulating Arrays and the data within. For many of the basic math functionality of arrays such as addition, and multiplication can be used via dot operators (format of .+).

In [35]:

```
alpha = [8 4 2;4 2 1; 2 1 0] #2D syntactic Sugar
```

Out[35]:

In [36]:

```
alpha[2:3,:]
```

Out[36]:

In [37]:

```
alpha[1,:]
```

Out[37]:

In [40]:

```
n = zeros(3,3,3)
```

Out[40]:

In [41]:

```
n[:,2, 1:2] = 1
```

Out[41]:

In [42]:

```
n
```

Out[42]:

In [38]:

```
(ones(3) .+ alpha)
```

Out[38]:

In [39]:

```
(ones(2) .+ alpha[2:3,2:3])
```

Out[39]:

So in addition to tasks there is parallel computing, which allows the user to run functions on seperate CPUs or even seperate machines entirely. The processes (procs) are called seperately and pre-set with their functions up on the scheduler.

new processes are started with addprocs(n), then they are given their new functions with remotecall() which returns a future value that can be evaluated and have data extraced via fetch()

There are also remote channels which are writable for more control over syncing processes.

In [43]:

```
addprocs(1)
```

In [44]:

```
fut = remotecall(sqrt, 2, 2)
```

Out[44]:

In [45]:

```
fetch(fut)
```

Out[45]:

In [46]:

```
future = @spawn sqrt(2)
```

In [47]:

```
fetch(future)
```

Out[47]:

In [48]:

```
addprocs(2)
```

Out[48]:

In [49]:

```
@everywhere function fib(n)
if (n < 2)
return n
else
return fib(n-1) + fib(n-2)
end
end
```

In [50]:

```
@everywhere function fib_parallel(n)
if (n < 35)
return fib(n)
else
x = @spawn fib_parallel(n-1)
y = fib_parallel(n-2)
return fetch(x) + y
end
end
```

In [51]:

```
@time fib(42)
```

Out[51]:

In [52]:

```
@time fib_parallel(42)
```

In [53]:

```
count1 = makecounter(3)
```

Out[53]:

In [54]:

```
futu = remotecall(count1, 2)
```

Out[54]:

In [55]:

```
fetch(futu)
```

Out[55]:

In julia the main way that messages are passed between coroutines in Julia are produce() and consume(). This allows a two functions to pass a single bit of data back and forth to each other in a simple way.

Julia tasks have very little overhead but will always run on the same CPU. The coroutines are just a control structure to get the process to run between different threads for things like syncing.

In [56]:

```
function ping()
println("ping")
println(consume(pingreply))
end
```

Out[56]:

In [57]:

```
function pong(message::String)
while true
produce(message)
end
end
```

Out[57]:

In [58]:

```
pingreply = Task(() -> pong("pong"))
```

Out[58]:

In [59]:

```
ping()
```

In [60]:

```
for i in 1:4
ping()
end
```

This is handling tasks manually, instead of letting the scheduler do it. At this stage the tasks are not even known to the scheduler so things like deadlock won't be caught at all.

To add things to be automatically scheduled using schedule(), or the @schedule and @sync macros

This starts to get into how the language actually functions and features of that.

Our code starts off as a string. This code is then parsed into a format called an expression (expr)

ability to replace things in strings with variables or equations. it happens within a string via using the key $ with parenthesis

In [61]:

```
println("1 plus 2 equals $(1+2)")
```

In [62]:

```
a = 1
b = 2
```

Out[62]:

In [63]:

```
println("a and b are $a and $b respectively so a + b = $(a+b)")
println("the sqrt of b (val: $b) is $(sqrt(b))")
```

Julia gives access to allow you to change code based on input. By being able to parse and evaluate code at run time were are able to build powerful meta programs that will be able to respond to the needs of the user at run time.

This start with how Julia intreprets code that it is given. It uses the Parse() and Eval() commands to be able to transform a string into runable code.

All code in julia starts as a string that is then parsed into expressions

In [64]:

```
tobeparsed = "1 + 1"
```

Out[64]:

In [65]:

```
tobeEval = parse(tobeparsed)
```

Out[65]:

tobeEval uses the Expr data type which is the basic format for code to be computed in Julia.

In [66]:

```
eval(tobeEval)
```

Out[66]:

This is then evaluated into the final result

Having access to this middle step allows us to be able to modify our own functions.

In [78]:

```
function not_pre_set(op::String, num1::Real, num2::Real)
parse("$op($num1, $num2)")
end
```

Out[78]:

In [79]:

```
not_pre_set("+", 1, 2)
```

Out[79]:

In [80]:

```
eval(not_pre_set("+", 1, 2))
```

Out[80]: