Skip to content

Latest commit

 

History

History
635 lines (504 loc) · 13.1 KB

Julia.md

File metadata and controls

635 lines (504 loc) · 13.1 KB

Julia

Variables

# Defn
x = 5

# Can redefine constants which is not currently in use
pi = 5

# Operators in brackets
(+) = f # Reassigns the operator

_ = a # Discards a
a = _ # Error

# Assignment chaining
# Assignment is basically binding value to variable
a = (b = 5) + 3 # a=8, b=5

a = []
b = a # Binds b to the same array (no copy made)

c = nothing # Equivalent to None in python
  • Stylistic conventions
    • Variables are snake_case
    • Types, modules are PascalCase
    • Functions, macros are lowercase w/o underscore
    • Mutating functions end in !

Integers

# Types
Int8, UInt8, Int128, Bool, Float16, Float64

# Size is arch dependent
typeof(1) # Int64 in 64 bit

# Diff base
a = 0x123 # typeof returns UInt
b = 0b101011
c = 0o1723
d = -0x23

# Max/min repr values
typemin(Int32)

# Floats
a = 1e10
a = 1.32
b = 0x1p0

a = Inf
b = NaN

a = big"133444444444444444444444444"
a = BigInt(133444444444444444444444444)

# Easy poly exprs
2x^2 + 3x + 1
# 2^3x = 2^(3x)
# 2x^3 = 2(x^3)

a = (x-1)x
a = x(x-1) # Error due to fn application
a = 2(x+y) # Works because literal

zero(Float32) # 0.0f0
one(Int32) # 1
  • Overflow results in wraparound
  • BigInt, BigFloat available but type promotion has to be explicitly stated

Math Operations & Functions

x / y # divide
x (division symbol) y # int divide
x \ y # inverse divide
x ^ y # power

NaN * false # 0.0
false * Inf # 0.0

x (|| or &&) y # Short circuited

x += 1

# Every binary op has a corresponding vectorized dot op
# Unary like ! and (root) too
[1, 2, 3] ^ 3 # Error
[1, 2, 3] .^ 3 # Elementwise mult

# Nested dot calls are fused & adjacent are converted to nested forms
x .+ 3 .* x.^2 = (+).(x, (*).(3, (^).(x, 2)))

# @. macro applies dot op to all elements inside
2 .* A.^2 .+ sin.(A) = @. 2A^2 + sin(A)

# Updating op too support dot
a .+= b or @. a += b = a .= a .+ b

1 .+ x # Error

# NaN is not equal to anything
NaN == NaN # False
[1, NaN] == [1, NaN] # False
isequal([1, NaN], [1, NaN]) # True & should be used
isinf(), isfinite(), isnan() # Other useful funcs

# Comparisons can be chained like python & dotted
# Order of comparison is undefined
0 .< A .< 1
1 < 2 <= 3 # true
1 > 2 <= 3 # false

# Op precedence (:x are symbols)
Base.operator_precedence(:+) # 11
Base.operator_associativity(:-) # :left

# Numeric conversions
T(x) or convert(T, x) # Int8(323) - Error
x % T # Converts int types, overflows if too big w/o error
round(Int, x) = Int(round(x))

# sinpi, cospi provide better repr of sin(pi*x)
a = sinpi(x)
a = sind(x) # In degrees (default is radian)

Complex & Rational numbers

# Complex numbers
1 + 2im
(2 + 3im) - 1 # 1 + 3im
3/4im = 3 / (4im)
real(), imag(), conj(), angle(), abs(), abs2() # abs squared
complex(a, b) # Use over a + b*im

# Rational numbers (not floats!!!)
# Always reduced to lowest form
2 // 3 # Construction
numerator(), denominator()
2//3 == 6//9 # true
0 // 0 # Error

Strings

# Char
a = 'x'
b = Int(a) # 120, Int64
c = '\u0ff' or '\U10ffff'
d = '\n' or '\t'

# Strings
a = "hi" or """hello"""
b = "hellllooooo \
	hi"
c = """jefej
	ejofefj"""
	
c = a[1] or a[begin] or a[end]
d = b[1:2] # Range indexing & makes a copy
e = SubString(b, 1, 2) or @views b[1:2] # Creates a view, not a copy

for c in s
	println(c)
end

nextind(), prevind(), eachindex() # To find valid indices
codeunit(s, i) # Access raw codeunit

# Concatenation
c = string(a, ", ", b)
c = a * ", " * b # * used rather than + to show noncommutativity

# Interpolation
a = 234
b = "$(a + 1) $a"

# Functions
findfirst(c, s), findlast(c, s), findnext(c, s, i)
occursin(s1, s2)
repeat(s, times), replace(s1, func)
join(vec, s, last_s)
length(s)

# Regex
reg = r"^\s" or Regex(str)
occursin(reg, s), match(reg, s)

# Byte array literals
bal = b"hello" # UInt8 array

# Version literals
ver = v"0.2"

# Raw literals
r = raw"huehfu" # Extra quotation should be escaped
  • String supports full UTF-8
  • All string types are subtypes of AbstractString & all char types of AbstractChar (It is better to use these in function definitions)
  • Strings are immutable
  • Strings are byte indexed, not char indexed
  • Characters are 32 bit primitive
  • Indexing starts from 1, not 0
  • Not every index into a String is necessarily a valid char
  • Number of chars in String is not always same as last index (due to variable length encoding)

Functions

# Definitions
function f(x, y)
	x + y
end

f(x, y) = x + y

# Calling
f(2, 3)
g = f
g(2, 3)

# Arg behaviour
function f!(x::Vec, y::Integer)
	x[1] = 5 # Mutates
	y = 7 # No mutation of og object

# Return types
# Return type enforced and return value is converted
function g(x) :: Int8 
	x
end

# Operator fn
# Except for &&, || all other can be used as fn
+(1, 2, 3) # 6

# Anon fn
x -> x^2 + x + 1
() -> 3
(x, y) -> x + y

# Tuples
a = (1, 2, 3) # Immutable, fixed length containers
b = (1,)
println(a[0])

# Named tuples
x = (a=2, b=3)
x[0] or x.a

# Destructuring
(a, b, c) = [1, 2, 3] # RHS has to be an iterable
_, _, c = [1, 2, 3]
arr[0], b = [1, 2] # arr[0] is mutated

# Slurping
# Assignment of a collection of remaining elements
a, b..., c = "hello" # a=h, b=ell, c=o

# Argument destructuring
f((x, y)) = x + y
f((2, 3))

# Property destructuring
foo((; x, y)) = x + y
foo((x=2, y=2)) # Advantage is that both structs and namedtuples can be passed

# Varargs
# In definition
foo(a, b, x...) = x # x is a tuple
# In caller
x = (2, 3, 4)
foo(1, x...) # a=1, b=2, x=(3, 4), the callee need not be a varargs function

# Optional args
function x(a, b=1, c=1)
	a
end
x(1)
x(1, 2)
x(1, 2, 3)
methods(x) # Shows all possible methods of fn x

# Keyword args
function x(a, b; c=1, d=1, kwargs...) # kwargs collects extra keyword args (a immutable key value iterator)
	a
end
x(1, 2, c=1, d=2)
x(1, 2, c=1, :d=>2)
# In case kwargs has same name as other keyword arg, that arg takes pref

# Concise do blocks
# do creates an anon fn
# do a, b = 2 args & do (a, b) = tuple arg
# do can capture vars from outer scope and automatically close streams, etc
map(x->x, [])
map([]) do x
	x
end

# Composition & piping
(f \circ g)(x) = f(g(x))
1:10 |> sum |> sqrt
["a", "b"] .|> [uppercase, lowercase]
  • Functions are first class objects that map tuple of arguments to a return value
  • Not pure
  • No explicit return required
  • Values are not copied to arguments, rather are shared. Arguments act like new bindings to the values (like Python)
  • Mutating function identifiers end in !
  • Reasons to have arg types
    • No perf impact
    • Correctness & Clarity
    • Dispatch - can have different versions of a fn for diff arg types
  • Do not overly restrict types & omit when not sure
  • Return types are not usually used and instead automatically inferred
  • Operators with special names

Control Flow

# Compound exprs
z = begin
	x = 1
	y = 2
	x + y
end
z = (x=1; y=2; x+y)

# Conditional eval
if x < y
	...
elseif x > y
	...
else 
	...
end

a ? b : c

# Short circuits
n >= 0 || error("n must be positive")

# Loops
i = 0
while i <= 3
	global i += 1
end

for i = 1:3 # 1:3 is a range object
	...
end
for i in []
	...
end
for i = 1:2, j = 3:i # Nested loop, break exits both loops
	...
end
for (i, j) in zip([], []) ...

# Exceptions
struct Custom <: Exception 
	var::Symbol
end # Custom exception
throw(DomainError(1, ""))
error("") # Stops execution
try
	...
catch e
	if isa(e, DomainError) ...
else # Used instead of extending try so as to not catch more errors
	...
finally
	...
end
  • if blocks are leaky, i.e they do not introduce a local scope. New variables defined can be used later
  • if blocks return values
  • 1/0 do not convey boolean meanings
  • &&, || are short circuited, second exprs are not evaluated if first satisfies the operator
  • &, | are not short circuited

Scope

# Local scope

# Constants (best used with globals, local consts are not supported)
const pi = 3.14
const a, b = 1, 2
x::Float64 = 2.7 # Automatically a constant if globally declared
  • Two types - global and local
  • if does not introduce a new scope
  • Inner scopes can see variables in outer scopes
  • Modules
    • Introduce new global scopes
    • Variables present can't be mutated from other modules directly
  • Constants
    • Not supported by local vars
    • Structs are const by default

Types

# Types
(1+2)::Int # Asserts type
a::Int = 1 + 2 # Specifies/Declares type

# Abstract types
abstract type Number end
abstract type Signed <: Integer end # <: specifies supertype (defaults to Any)

Integer <: Number # true

# Primitives
primitive type Float16 <: AbstractFloat 16 end # Specify bits

# Composite types
struct Foo
	bar
	baz::Int
end
mutable struct A
	a
	const b::Int
end
foo = Foo("", 1) # Constructor
foo.bar # ""
fieldnames(Foo) # (:bar, :baz)

# Type unions
IntOrStr = Union{Int, AbstractString}
Option = Union{Nothing, Int} # Like Option<T>

# Parametric types (types with parameters)
# Composite
struct Point{T}
	x::T
	y::T
end
Point{AbstractString} <: Point # true
Point{<:AbstractString} # Subtypes also accepted now
Point{Float64}(1.0, 2.0)
# Abstract
abstract type Pointy{T, X} end
abstract type Pointy{T<:Real} end # Guard

# Tuple types
Tuple{Int, String}
Tuple{Int, Vararg{String}} # Variable arg type

# Named tuple types
@NamedTuple{a::Int, b::String}
@NamedTuple begin 
	a::Int 
	b::String
end 

# UnionAll types
  • Julia is dynamically typed, is nominative and parametric
  • Has method dispatch with on types of args
  • All concrete types are final and can only have abstract types are supertypes
  • All values in Julia are true objects
  • No compile time type exists
  • Variables don't have types, only values do
  • Abstract types
    • Can't be instantiated
    • Any is at the top of the type graph and Union{} at the bottom
    • Gives default impls for concrete types (make it generic)
  • Primitive types
    • Do not make your own
  • struct is immutable but contained objects that are mutable remain mutable. Mutables are alloc on heap
  • All 3 types are repr internally as DataType
  • Type params are invariant
    • Float64 <: Real but P{Float64} not subtype of P{Real}
  • Tuple type params are covariant

Methods

# Defn
f(x::Float64) = 2x
f(x::Number) = 2x
f(2.0)
f(2)

# Parametric methods
same_type(x::T, y::T) where {T} = true
same_type(x,y) = false
same_type_numeric(x::T, y::T) where {T<:Number} = true # Guard

# Redefining methods

# Design patterns
  • Definition of one possible behaviour of a function is called a method
  • When a function is applied to a particular tuple of arguments, the most specific method applicable to those arguments is applied.
  • The choice of which method to execute when a function is applied is called dispatch.
  • Multiple dispatch used where all args are considered (unlike other langs where only first considered)
  • The first method definition for a function creates the function object, and subsequent method definitions add new methods to the existing function object. Creating multiple methods is called specialization
  • Other than explicit method decl, Julia implicitly creates methods at compile time based on arg types

Modules

module FastThings
end

# Qualification
Base.sin()
Base.:+ # Needs a colon for operators

# Exports
module A
export b, C
const C = 5
b(x) = x
end

# Using modules
using A # Adds elements of export list to global namespace
using .A # Load module from locally defined module
import A # Allows only qualified access (export list does not matter)
import A as B
import A:a as b
using .A:a, b, A # Specific name imports (specify modulename itself to import it too)

# Adding methods
using .A or import .A:a # using .A:a won't work
A.a() = ...

# Baremodules (w/o containing Base, eval and include)
baremodule A

# Submodules
module A
module B
import ..C:a # Each . = parent
end
end
  • They are separate namespaces and introduce a global scope
  • Can export and import names
  • Can be precompiled
  • Can have multiple modules per file and vice versa
  • PascalCase and plural
  • Standard modules
    • Core - builtins
    • Base - base functionality
    • Main - top level and current module

Documentation

"Info" # Interpreted as markdown
foo(x) = x

# Enables to use raw, and other types of string including interpolation
@doc raw"""
"""
f(x) = x

@doc(@doc foo!)foo # Use foo!'s documentation

Metaprogramming

  • Lisp style macros operating at the level of AST

Arrays

zeroes(Int8, (2, 3))
ones(...), trues(), falses()
reshape(), copy(), deepcopy()
range(), fill!()
Matrix{T}()

# Array literals
[1, 2, 3] # Tries to promote if diff types, else heterogeneous
Int8[1, 2, 3]

# Concatenation
[1:2, 4:5] # 2D array
[1:2; 4:5] # Vertically concats (1, 2, 4, 5)'
[1:2;; 4:5] # Horizontally concat (1, 2, 4, 5)

# Comprehensions
[x + y for x=1:2, y=2:3 if x + y == 2]
[(i,j) for i=1:3 for j=1:i] # Nested loop

# Generators
(i*2 for i=1:10)

# Indexing
a[1, 2]
a[(1, 2)]
x[2:3, 2:end-1] 

# Indexed assignment
x[2, 3] = 1
x[CartesianIndex(1, 2, 3)] # Groups indices into one obj
x[[false, true], :] # Boolean indexing
x[map(ispow2, x)]
x[5] # If x is multidim, a single element is returned in column major form
  • Need not do vectorized impl for perf
  • Using eachindex(a) is better than 1:length(a)