Julia Study Notes
November 18, 2014
Getting Started
- Julia REPL:
julia
- In Julia REPL, expressions are evaluated immediately. Only those without trailing semicolon is shown.
- Whether or not the results are shown, a special variable
ans
is always bounded to the value of the last evaluated expression. It’s only available in REPL. - To evaluate a file:
include("file.jl")
- Run Julia source files from the shell:
julia script.jl arg1 arg2 ...
. - Command-line arguments are avaiable inside the script by the global constant
ARGS
. - Run Julia code inside a string:
julia -e 'for x in ARGS; println(x); end' foo bar
- Start Julia in parallel mode with the
-p
or--machinefile
options.-p n
will launch an additional n worker processes, while--machinefile file
will launch a worker for each line in filefile
. - Julia initialization file:
~/.juliarc.jl
Variables
- Variables in Julia are names bounded to a value.
- Case-sensitive.
- Unicode (UTF-8) names are allowed.
- Input LaTeX-style symbol names like
\delta
by tab-completion. - Built-in variable
pi
. - The only explicitly disallowed names for variables are the names of built-in statements.
- Style convention:
- names of variables are in lower case with underscores;
- names of types begin with a capital letter with CamelCase;
- names of functions and macros are in lower case, without underscores;
- functions that modify their inputs have names that end in
!
.
Integers and Floating-Point Numbers
- Integer types:
Int8, Uint8, Int16, Uint16, Int32, Uint32, Int64, Uint64, Int128, Uint128, Bool, Char
- Char natively supports representation of Unicode characters.
- Floating-point types:
Float16, Float32, Float64
- All numeric types interoperate naturally without explicit casting, thanks to a flexible, user-extensible type promotion system
- The default type for an integer literal depends on whether the target system has a 32-bit architecture or a 64-bit architecture
typeof(1)
- The Julia internal variable
WORD_SIZE
indicates whether the target system is 32-bit or 64-bit. - Larger integer literals that cannot be represented using only 32 bits but can be represented in 64 bits always create 64-bit integers, regardless of the system type
- Unsigned integers are input and output using the
0x
prefix and hexadecimal. - The size of the unsigned value is determined by the number of hex digits used.
- Binary and octal literals are also supported, by using the
0b
and0o
prefixes. - The minimum and maximum representable values of primitive numeric types such as integers are given by the
typemin
andtypemax
functions - In Julia, exceeding the maximum representable value of a given type results in a wraparound behavior.
- Integer division (the
div
function) has two exceptional cases: dividing by zero, and dividing the lowest negative number (typemin
) by -1. Both of these cases throw aDivideError
. The remainder and modulus functions (rem
andmod
) throw a DivideError when their second argument is zero. - Floating-point literals are all
Float64
values. LiteralFloat32
values can be entered by writing anf
in place ofe
. - Convert:
float32(-1.5)
- Half-precision floating-point numbers are also supported (
Float16
), but only as a storage format. In calculations they’ll be converted toFloat32
. - Floating-point numbers have two zeros, positive zero and negative zero. They are equal to each other but have different binary representations.
- Use
bits
function to see a variable’s binary expression. - Special floating-point values:
Inf16, Inf32, Inf, -Inf16, -Inf32, -Inf, NaN16, NaN32, NaN
. - Julia provides the
eps
function, which gives the distance between1.0
and the next larger representable floating-point value:eps(Float32); eps(Float64); eps();
- The
eps
function can also take a floating-point value as an argument, and gives the absolute difference between that value and the next representable floating point value. That is,eps(x)
yields a value of the same type asx
such thatx + eps(x)
is the next representable floating-point value larger thanx
. - The distance between two adjacent representable floating-point numbers is not constant, but is smaller for smaller values and larger for larger values.
- Julia also provides the
nextfloat
andprevfloat
functions which return the next largest or smallest representable floating-point number to the argument respectively. - If a number doesn’t have an exact floating-point representation, it must be rounded to an appropriate representable value. The default mode is to round to the nearest representable value.
- Julia allows variables to be immediately preceded by a numeric literal, implying multiplication:
2x^2 - 3x + 1; 2^2x
- The precedence of numeric literal coefficients is the same as that of unary operators such as negation. So
2^3x
is parsed as2^(3x)
, and2x^3
is parsed as2*(x^3)
- Numeric literals also work as coefficients to parenthesized expressions:
2(x-1)^2 - 3(x-1) + 1
- Parenthesized expressions can be used as coefficients to variables, implying multiplication of the expression by the variable:
(x-1)x
- Neither juxtaposition of two parenthesized expressions, nor placing a variable before a parenthesized expression, however, can be used to imply multiplication:
(x-1)(x+1); x(x+1)
- No whitespace may come between a numeric literal coefficient and the identifier or parenthesized expression which it multiplies.
- Expressions starting with 0x are always hexadecimal literals. Expressions starting with a numeric literal followed by e or E are always floating-point literals.
- Literal zero of type
x
or type of variablex
:zero(x)
; Literal one of typex
or type of variablex
:one(x)
.
Mathematical Operations and Elementary Functions
- Inverse divide:
x/y
equivalent toy/x
. - Positive zero is equal but not greater than negative zero.
NaN
is not equal to, not less than, and not greater than anything, including itself.- Test:
isfinite(x); isinf(x); isnan(x);
- Comparisons can be arbitrarily chained:
1 < 2 <= 2 < 3 == 3 > 2 >= 1 == 1 < 3 != 5
- Chained comparisons use the
&&
operator for scalar comparisons, and the&
operator for elementwise comparisons, which allows them to work on arrays. - The operator
.<
is intended for array objects; the operationA .< B
is valid only if A and B have the same dimensions. - The operator returns an array with boolean entries and with the same dimensions as A and B. Such operators are called elementwise.
- The middle expression is only evaluated once, rather than twice as it would be if the expression were written as
v(1) < v(2) && v(2) <= v(3)
. - The order of evaluations in a chained comparison is undefined. It is strongly recommended not to use expressions with side effects (such as printing) in chained comparisons.
- Rounding functions:
round(x), iround(x), floor(x), ifloor(x), ceil(x), iceil(x), trunc(x), itrunc(x)
- Division functions:
div(x,y), fld(x,y), cld(x,y), rem(x,y), divrem(x,y), mod(x,y), mod2pi(x), gcd(x,y...), lcm(x,y...)
- Sign and absolute value functions:
abs(x), abs2(x), sign(x), signbit(x), copysign(x,y), flipsign(x,y)
- Powers, logs and roots:
sqrt(x), cbrt(x) , hypot(x,y), exp(x), expm1(x), ldexp(x,n), log(x), log(b,x), log2(x), log10(x), log1p(x), exponent(x), significand(x)
- Trigonometric and hyperbolic functions:
atan2(x, y), sinpi(x), cospi(x), sind(x), cosd(x)
- Special functions:
gamma(x), lgamma(x), beta(x,y), lbeta(x,y)
Complex and Rational Numbers
- The global constant
im
is bound to the complex number i, representing the principal square root of -1. - Standard functions to manipulate complex values are provided:
real, imag, conj, abs, abs2, angle
- Note that mathematical functions typically return real values when applied to real numbers and complex values when applied to complex numbers. For example,
sqrt
behaves differently when applied to-1
versus-1 + 0im
even though-1 == -1 + 0im
. - Use the
complex
function instead to construct a complex value directly from its real and imaginary partscomplex(a,b)
to avoid the multiplication and addition operations. - Rationals are constructed using the
//
operator:2//3
- If the numerator and denominator of a rational have common factors, they are reduced to lowest terms such that the denominator is non-negative.
- The standardized numerator and denominator of a rational value can be extracted using the
num
andden
functions. - Constructing infinite rational values is acceptable. Trying to construct a NaN rational value, however, is not.
Strings
String
is an abstraction, not a concrete type — many different representations can implement theString
interface, but they can easily be used together and interact transparently. Any string type can be used in any function expecting aString
.- Julia has a first-class type representing a single character, called
Char
. - Strings are immutable.
- Conceptually, a string is a partial function from indices to characters — for some index values, no character value is returned, and instead an exception is thrown.
- Julia supports the full range of Unicode characters: literal strings are always ASCII or UTF-8 but other encodings for strings from external sources can be supported.
- For performance, the char conversion does not check that every character value is valid. If you want to check that each converted value is a valid code point, use the
is_valid_char
function - String literals are delimited by double quotes or triple double quotes.
- If you want to extract a character from a string, you index into it:
str[6]
- All indexing in Julia is 1-based: the first element of any integer-indexed object is found at index 1, and the last element is found at index n, when the string has a length of n.
- In any indexing expression, the keyword
end
can be used as a shorthand for the last index (computed byendof(str)
). - You can perform arithmetic and other operations with end, just like a normal value:
str[end-1]
- Notice that the expressions str[k] and str[k:k] do not give the same result.
- UTF-8 is a variable-width encoding, meaning that not all characters are encoded in the same number of bytes. This means that not every byte index into a UTF-8 string is necessarily a valid index for a character. If you index into a string at such an invalid byte index, an error is thrown.
- Because of variable-length encodings, the number of characters in a string (given by
length(s)
) is not always the same as the last index. If you iterate through the indices 1 throughendof(s)
and index intos
, the sequence of characters returned when errors aren’t thrown is the sequence of characters comprising the strings
. Thus we have the identity thatlength(s) <= endof(s)
, since each character in a string must have its own index. - You can just use the string as an iterable object, no exception handling required:
for c in s ...
- Julia also provides
UTF16String
andUTF32String
types, constructed by theutf16(s)
andutf32(s)
functions respectively, for UTF-16 and UTF-32 encodings. - String concatenation:
string(greet, ", ", whom, ".\n")
- Julia allows interpolation into string literals using
$
:"$greet, $whom.\n"
. you can interpolate any expression into a string using parentheses:"1 + 2 = $(1 + 2)"
- String functions:
search, repeat, next, ind2char, char2ind
- Create regular expression:
r"^\s*(?:#|$)"
, or with modifications:r"a+.*b+.*?d$"ism
- To check if a regex matches a string, use the ismatch function:
ismatch(r"^\s*(?:#|$)", "not a comment")
- To capture information about a match, use the match function instead:
match(r"^\s*(?:#|$)", "# a comment")
- If the regular expression does not match the given string,
match
returnsnothing
nothing
is a special value that does not print anything at the interactive prompt. Other than not printing, it is a completely normal value and you can test for it- programmatically.
- If a regular expression does match, the value returned by
match
is aRegexMatch
object. You can extract the following info from aRegexMatch
object:- the entire substring matched:
m.match
- the captured substrings as a tuple of strings:
m.captures
- the offset at which the whole match begins:
m.offset
- the offsets of the captured substrings as a vector:
m.offsets
- the entire substring matched:
- Triple-quoted regex strings, of the form
r"""..."""
, are also supported (and may be convenient for regular expressions containing quotation marks or newlines). - Version numbers can easily be expressed with non-standard string literals of the form
v"..."
. Version number literals createVersionNumber
objects. VersionNumber
objects are composed of major, minor and patch numeric values, followed by pre-release and build alpha-numeric annotations. For example,v"0.2.1-rc1+win64"
is broken into major version 0, minor version 2, patch version 1, pre-release rc1 and build win64.- The global constant VERSION holds Julia version number as a VersionNumber object.
Functions
- The basic syntax for defining functions in Julia is:
function f(x,y) \n x + y \n end
. - There is a second, more terse syntax for defining a function in Julia:
f(x,y) = x + y
. - In the assignment form, the body of the function must be a single expression, although it can be a compound expression.
- A function is called using the traditional parenthesis syntax.
- Without parentheses, the expression f refers to the function object, and can be passed around like any value.
- The
apply
function applies its first argument — a function object — to its remaining arguments. - Julia function arguments follow a convention sometimes called “pass-by-sharing”, which means that values are not copied when they are passed to functions. Function arguments themselves act as new variable bindings (new locations that can refer to values), but the values they refer to are identical to the passed values. Modifications to mutable values (such as Arrays) made within a function will be visible to the caller.
- The value returned by a function is the value of the last expression evaluated. The
return
Keyword. - In Julia, most operators are just functions with support for special syntax. The exceptions are operators with special evaluation semantics like
&&
and||
. - You can also apply them using parenthesized argument lists, just as you would any other function:
+(1,2,3)
- The infix form is exactly equivalent to the function application form — in fact the former is parsed to produce the function call internally.
- You can assign and pass around operators such as
+
and*
just like you would with other function values:f = +; f(1,2,3)
- Under the name
f
, the function does not support infix notation, however. - Operators with special names:
hcat
:[A B C ...]
vcat
:[A, B, C, ...]
hvcat
:[A B; C D; ...]
ctranspose
:A’
transpose
:A.’
colon
:1:n
getindex
:A[i]
setindex!
:A[i]=x
- Anonymous function:
x -> x^2 + 2x - 1
- A zero-argument anonymous function is written as
()->3
. - In Julia, one returns a tuple of values to simulate returning multiple values. However, tuples can be created and destructured without needing parentheses, thereby providing an illusion that multiple values are being returned, rather than a single tuple value:
function foo(a, b) \n a + b, a * b \n end; x, y = foo(2,3);
- You can define a varargs function by following the last argument with an ellipsis:
bar(a,b,x...) = (a,b,x)
.x
is bound to a tuple of the trailing values passed tobar
. - On the flip side, it is often handy to “splice” the values contained in an iterable collection into a function call as individual arguments. To do this, one also uses … but in the function call instead:
bar(1,2,x...)
- The iterable object spliced into a function call need not be a tuple. Also, the function that arguments are spliced into need not be a varargs function (although it often is).
- Optional arguments:
function parseint(num, base=10)
- Functions with keyword arguments are defined using a semicolon in the signature:
function plot(x, y; style="solid", width=1, color="black")
- Extra keyword arguments can be collected using …, as in varargs functions:
function f(x; y=0, args...)
- When optional argument default expressions are evaluated, only previous arguments are in scope. In contrast, all the arguments are in scope when keyword arguments default expressions are evaluated.
- Block syntax:
x -> begin \n .... \n end
do
syntax:func(args) do x \n ... \n end
– creates an anonymous function with argumentx
and passes it as the first argument tofunc
Control Flow
- There are two Julia constructs that accomplish compound expressions:
begin
blocks and(;)
chains. The value of both compound expression constructs is that of the last subexpression:z = begin \n x = 1 \n y = 2 \n z = x + y \n end
, orz = (x = 1; y = 2; x + y)
- This syntax is particularly useful with the terse single-line function definition form of functions.
if-elseif-else
- It is an error if the value of a conditional expression is anything but
true
orfalse
a ? b : c
- Short-circuit evaluation is frequently used in Julia to form an alternative to very short if statements.
while i <= 5
;for i = 1:5
- If the variable
i
has not been introduced in an other scope, in thefor
loop form, it is visible only inside of thefor
loop, and not afterwards. for i in [1,4,0]
- Multiple nested
for
loops can be combined into a single outer loop, forming the cartesian product of its iterables:for i = 1:2, j = 3:4
. Abreak
statement inside such a loop exits the entire nest of loops, not just the inner one. - Built-in exceptions:
ArgumentError, BoundsError, DivideError, DomainError, EOFError, ErrorException, InexactError, InterruptException, KeyError, LoadError, MemoryError, MethodError, OverflowError, ParseError, SystemError, TypeError, UndefRefError, UndefVarError
throw(DomainError())
- The
error
function is used to produce an ErrorException that interrupts the normal flow of control. - Julia also provides other functions that write messages to the standard error I/O, but do not throw any Exceptions:
info("Hi"); warn("Hi");
- The exception is much slower than simply comparing and branching.
try ... catch e \n if isa(e, DomainError) ... end \n finally ... end
Scope of Variables
- Certain constructs in the language introduce scope blocks, including
function
bodies,while
loops,for
loops,try
blocks,catch
blocks,let
blocks,type
blocks. - Notably missing from this list are
begin
blocks, which do not introduce new scope blocks. - When a variable is introduced into a scope, it is also inherited by all inner scopes unless one of those inner scopes explicitly overrides it.
- A declaration
local x
orconst x
introduces a new local variable. - A declaration
global x
makesx
in the current scope and inner scopes refer to the global variable of that name. - A function’s arguments are introduced as new local variables into the function’s body scope.
- An assignment
x = y
introduces a new local variablex
only ifx
is neither declaredglobal
nor introduced aslocal
by any enclosing scope before or after the current line of code. - A variable that is not assigned to or otherwise introduced locally defaults to global.
- Since functions can be used before they are defined, as long as they are defined by the time they are actually called, no syntax for forward declarations is necessary, and definitions can be ordered arbitrarily.
- Anonymous functions are not closures – the value of their variables are evaluated at run time. To create closures, use
let i = i \n f(i) = () -> i
which copys the current value ofi
to a variable in a new scope which happens to be namedi
too, and bound thati
to the function. for
loops and comprehensions have a special additional behavior: any new variables introduced in their body scopes are freshly allocated for each loop iteration.- The
const
declaration is allowed on both global and local variables, but is especially useful for globals. If a global variable will not change, adding aconst
declaration solves this performance problem. - Note that const only affects the variable binding; the variable may be bound to a mutable object (such as an array), and that object may still be modified.
Types
- Julia’s type system is dynamic, but gains some of the advantages of static type systems by making it possible to indicate that certain values are of specific types.
- The default behavior in Julia when types are omitted is to allow values to be of any type.
- Describing Julia in the lingo of type systems, it is: dynamic, nominative and parametric.
- One particularly distinctive feature of Julia’s type system is that concrete types may not subtype each other: all concrete types are final and may only have abstract types as their supertypes.
- There is no division between object and non-object values: all values in Julia are true objects having a type that belongs to a single, fully connected type graph, all nodes of which are equally first-class as types.
- There is no meaningful concept of a “compile-time type”: the only type a value has is its actual type when the program is running.
- Only values, not variables, have types — variables are simply names bound to values.
- Both abstract and concrete types can be parameterized by other types. They can also be parameterized by symbols, by values of any type for which
isbits
returnstrue
. - The
::
operator can be used to attach type annotations to expressions and variables in programs. - When appended to an expression computing a value, the
::
operator is read as “is an instance of”. It can be used anywhere to assert that the value of the expression on the left is an instance of the type on the right. - If the type assertion is not true, an exception is thrown, otherwise, the left-hand value is returned.
- When appended to a variable in a statement context, the
::
operator means something a bit different: it declares the variable to always have the specified type, like a type declaration in a statically-typed language such as C. Every value assigned to the variable will be converted to the declared type using theconvert
function. - Currently, type declarations cannot be used in global scope, e.g. in the REPL, since Julia does not yet have constant-type globals.
- Abstract types cannot be instantiated, and serve only as nodes in the type graph, thereby describing sets of related concrete types: those concrete types which are their descendants.
- It is common for a piece of code to make sense, for example, only if its argument* are some kind of integer, but not really depend on what particular kind of integer.
- The
abstract
keyword introduces a new abstract type, whose name is given by«name»
. This name can be optionally followed by<:
and an already-existing type, indicating that the newly declared abstract type is a subtype of this “parent” type:abstract «name» <: «supertype»
- When no supertype is given, the default supertype is
Any
— a predefined abstract type that all objects are instances of and all types are subtypes of. None
is the exact opposite ofAny
: no object is an instance ofNone
and all types are supertypes ofNone
.- The
<:
operator can also be used in expressions as a subtype operator which returnstrue
when its left operand is a subtype of its right operand:Integer <: Number
- An important point to note is that there is no loss in performance if the programmer relies on a function whose arguments are abstract types, because it is recompiled for each tuple of argument concrete types with which it is invoked.
- Julia lets you declare your own bits types, rather than providing only a fixed set of built-in bits types:
bitstype 16 Float16 <: FloatingPoint
- Currently, only sizes that are multiples of 8 bits are supported.
- In Julia, all values are objects, but functions are not bundled with the objects they operate on. This is necessary since Julia chooses which method of a function to use by multiple dispatch, meaning that the types of all of a function’s arguments are considered when selecting a method, rather than just the first one.
- Organizing methods into function objects rather than having named bags of methods “inside” each object ends up being a highly beneficial aspect of the language design.
type Foo
- New objects of composite type
Foo
are created by applying theFoo
type object like a function to values for its fields:foo = Foo("Hello, world.", 23, 1.5)
- When a type is applied like a function it is called a constructor.
- Two constructors are generated automatically (these are called default constructors). One accepts any arguments and calls
convert
to convert them to the types of the fields, and the other accepts arguments that match the field types exactly. - You may find a list of field names using the
names
function. - You can access the field values of a composite object using the traditional
foo.bar
notation: - Composite types with no fields are singletons; there can be only one instance of such types.
- It is also possible to define immutable composite types by using the keyword
immutable
instead oftype
. - Such types behave much like other composite types, except that instances of them cannot be modified. Immutable types have several advantages:
- They are more efficient in some cases. Types like the Complex example above can be packed efficiently into arrays, and in some cases the compiler is able to avoid allocating immutable objects entirely.
- It is not possible to violate the invariants provided by the type’s constructors.
- Code using immutable objects can be easier to reason about.
- An immutable object might contain mutable objects, such as arrays, as fields. Those contained objects will remain mutable; only the fields of the immutable object itself cannot be changed to point to different objects.
- In deciding whether to make a type immutable, ask whether two instances with the same field values would be considered identical, or if they might need to change independently over time. If they would be considered identical, the type should probably be immutable.
- An object with an immutable type is passed around (both in assignment statements and in function calls) by copying, whereas a mutable type is passed around by reference.
- It is not permitted to modify the fields of a composite immutable type.
- Composite types, abstract types and bits types are internally represented as instances of the same concept, DataType, which is the type of any of these types:
typeof(Real)
- Every concrete value in the system is either an instance of some DataType, or is a tuple.
- The type of a tuple of values is the tuple of types of values:
- Accordingly, a tuple of types can be used anywhere a type is expected:
(1,"foo",2.5) :: (Int64,String,Any)
- Note that the empty tuple
()
is its own type. - One tuple type is a subtype of another if elements of the first are subtypes of the corresponding elements of the second.
- A type union is a special abstract type which includes as objects all instances of any of its argument types, constructed using the special
Union
function:IntOrString = Union(Int,String)
- The union of no types is the “bottom” type,
None
. - Types can take parameters, so that type declarations actually introduce a whole family of new types.
- All declared types (the
DataType
variety) can be parameterized, with the same syntax in each case. type Point{T}
- This single declaration actually declares an unlimited number of types:
Point{Float64}, Point{String}, Point{Int64}
, etc. Each of these is now a usable concrete type. Point
itself is also a valid type object.- What does
Point
by itself mean? It is an abstract type that contains all the specific instancesPoint{Float64}, Point{String}
, etc.: ` Point{Float64} <: Point` - Even though
Float64 <: Real
we DO NOT havePoint{Float64} <: Point{Real}
. - This is for practical reasons: while any instance of Point{Float64} may conceptually be like an instance of Point{Real} as well, the two types have different representations in memory:
- An instance of
Point{Float64}
can be represented compactly and efficiently as an immediate pair of 64-bit values; - An instance of
Point{Real}
must be able to hold any pair of instances ofReal
. Since objects that are instances ofReal
can be of arbitrary size and structure, in practice an instance ofPoint{Real}
must be represented as a pair of pointers to individually allocatedReal
objects.
- An instance of
- Since the type
Point{Float64}
is a concrete type equivalent toPoint
declared withFloat64
in place ofT
, it can be applied as a constructor accordingly:Point{Float64}(1.0,2.0)
- Only one default constructor is generated for parametric types, since overriding it is not possible. This constructor accepts any arguments and converts them to the field types.
- You can also apply
Point
itself as a constructor, provided that the implied value of the parameter typeT
is unambiguous:Point(1.0,2.0)
abstract Pointy{T}
- Parametric abstract types are invariant, much as parametric composite types are (all the auto-generated types are siblings):
Pointy{Float64} <: Pointy{Real} #false
type Point{T} <: Pointy{T}
- Given such a declaration, for each choice of
T
, we havePoint{T}
as a subtype ofPointy{T}
:Point{Float64} <: Pointy{Float64}
- This relationship is also invariant:
Point{Float64} <: Pointy{Real} #false
- Now both
Point{Float64}
andDiagPoint{Float64}
are implementations of thePointy{Float64}
abstraction, and similarly for every other possible choice of typeT
. This allows programming to a common interface shared by allPointy
objects, implemented for bothPoint
andDiagPoint
. abstract Pointy{T<:Real}
type Point{T<:Real} <: Pointy{T}
isa(A,Type{B})
is true if and only ifA
andB
are the same object and that object is a type.- Without the parameter,
Type
is simply an abstract type which has all type objects as its instances, including, of course, singleton types:Float64 <: Type{Float64} <: Type
- It allows one to specialize function behavior on specific type values. This is useful for writing methods (especially parametric ones) whose behavior depends on a type that is given as an explicit argument rather than implied by the type of one of its arguments.
bitstype 32 Ptr{T}
- The type parameter
T
is not used in the definition of the type itself — it is just an abstract tag, essentially defining an entire family of types with identical structure, differentiated only by their type parameter. Thus,Ptr{Float64}
andPtr{Int64}
are distinct types, even though they have identical representations. And of course, all specific pointer types are subtype of the umbrellaPtr
type. typealias Uint Uint64
typealias Vector{T} Array{T,1}
- The
isa
function tests if an object is of a given type and returnstrue
orfalse
. - The
typeof
function returns the type of its argument. - Types are all composite values and thus all have a type of
DataType
DataType
is its own type.super
reveals a type’s supertype. Only declared types (DataType
) have unambiguous supertypes.
Methods
- A definition of one possible behavior for 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. Julia allows the dispatch process to choose which of a function’s methods to call based on the number of arguments given, and on the types of all of the function’s arguments.
- Multiple dispatch is particularly useful for mathematical code.
- When defining a function, one can optionally constrain the types of parameters it is applicable to, using the
::
type-assertion operator:f(x::Float64, y::Float64) = 2x + y;
- Applying it to any other types of arguments will result in a “no method” error.
- To define a function with multiple methods, one simply defines the function multiple times, with different numbers and types of arguments.
- To find out what the signatures of those methods are use the
methods
function. - In the absence of a type declaration with
::
, the type of a method parameter isAny
by default, meaning that it is unconstrained since all values in Julia are instances of the abstract typeAny
. - It is possible to define a set of function methods such that there is no unique most specific method applicable to some combinations of arguments. In such cases, Julia warns you about this ambiguity, but allows you to proceed, arbitrarily picking a method.
- You should avoid method ambiguities by specifying an appropriate method for the intersection case.
- Method definitions can optionally have type parameters immediately after the method name and before the parameter tuple:
same_type{T}(x::T, y::T) = true;
- Method type parameters are not restricted to being used as the types of parameters: they can be used anywhere a value would be in the signature of the function or body of the function.
- You can also constrain type parameters of methods:
same_type_numeric{T<:Number}(x::T, y::T) = true
- Optional arguments are implemented as syntax for multiple method definitions. For example, this definition:
f(a=1,b=2) = a+2b
translates to the following three methods:f(a,b) = a+2b; f(a) = f(a,2); f() = f(1,2)
- Methods are dispatched based only on positional arguments, with keyword arguments processed after the matching method is identified.
Constructors
- Constructors are functions that create new objects — specifically, instances of Composite Types (page 85). In Julia, type objects also serve as constructor functions.
Foo(x) = Foo(x,x)
additional constructor methods declared as normal methods like this are called outer constructor methods. Outer constructor methods can only ever create a new instance by calling another constructor method.- An inner constructor method is much like an outer constructor method, with two differences:
- It is declared inside the block of a type declaration, rather than outside of it like normal methods.
- It has access to a special locally existent function called new that creates objects of the block’s type.
type OrderedPair \n x :: Real \n y :: Real \n OrderedPair(x,y) = x > y ? error("out of order") : new(x,y) \n end
- Once a type is declared, there is no way to add more inner constructor methods. Since outer constructor methods can only create objects by calling other constructor methods, ultimately, some inner constructor must be called to create an object.
- If any inner constructor method is defined, no default constructor method is provided.
- It is considered good form to provide as few inner constructor methods as possible: only those taking all arguments explicitly and enforcing essential error checking and transformation.
- Additional convenience constructor methods, supplying default values or auxiliary transformations, should be provided as outer constructors that call the inner constructors to do the heavy lifting.
Point
,Point{Float64}
andPoint{Int64}
are all different constructor functions. In fact,Point{T}
is a distinct constructor function for each typeT
.- Inner constructor declarations always define methods of
Point{T}
rather than methods of the generalPoint
constructor function. SincePoint
is not a concrete type, it makes no sense for it to even have inner constructor methods at all. It is this method declaration that defines the behavior of constructor calls with explicit type parameters likePoint{Int64}(1,2)
andPoint{Float64}(1.0,2.0)
. - The outer constructor declaration, on the other hand, defines a method for the general
Point
constructor which only applies to pairs of values of the same real type. This declaration makes constructor calls without explicit type parameters, likePoint(1,2)
andPoint(1.0,2.5)
, work. convert(Float64,x)
promote(x,y)...
Conversion and Promotion
- In a sense, Julia falls into the “no automatic promotion” category: mathematical operators are just functions with special syntax, and the arguments of functions are never automatically converted.
- “Automatic” promotion of mathematical operands simply emerges as a special application: Julia comes with pre-defined catch-all dispatch rules for mathematical operators, invoked when no specific implementation exists for some combination of operand types. These catch-all rules first promote all operands to a common type using user-definable promotion rules, and then invoke a specialized implementation of the operator in question for the resulting values, now of the same type.
- User-defined types can easily participate in this promotion system by defining methods for conversion to and from other types, and providing a handful of promotion rules defining what types they should promote to when mixed with other types.
- Conversion of values to various types is performed by the
convert
function. Theconvert
function generally takes two arguments: the first is a type object while the second is a value to convert to that type; the returned value is the value converted to an instance of given type:convert(Uint8, x)
- Conversion isn’t always possible, in which case a no method error is thrown indicating that
convert
doesn’t know how to perform the requested conversion. - Julia does not consider parsing strings as numbers or formatting numbers as strings to be conversions.
- To define a new conversion, simply provide a new method for
convert
. That’s really all there is to it.convert(::Type{Bool}, x::Number) = (x!=0)
- Notice the syntax used for the first argument: the argument name is omitted prior to the
::
symbol, and only the type is given. This is the syntax in Julia for a function argument whose type is specified but whose value is never used in the function body. - Promotion has nothing to do with the type hierarchy, and everything to do with converting between alternate representations. For instance, although every
Int32
value can also be represented as aFloat64
value,Int32
is not a subtype ofFloat64
. - Promotion to a common supertype is performed in Julia by the
promote
function, which takes any number of arguments, and returns a tuple of the same number of values, converted to a common type, or throws an exception if promotion is not possible. - The definition of catch-all methods for numeric operations like the arithmetic operators
+
,-
,*
and/
:+(x::Number, y::Number) = +(promote(x,y)...)
- The most common usages of promote occur in outer constructors methods, provided for convenience, to allow constructor calls with mixed types to delegate to an inner type with fields promoted to an appropriate common type.
- The behavior of promote is defined in terms of an auxiliary function called
promote_rule
, which one can provide methods for. Thepromote_rule
function takes a pair of type objects and returns another type object, such that instances of the argument types will be promoted to the returned type:promote_rule(::Type{Float64}, ::Type{Float32} ) = Float64
- The promotion type does not need to be one of the argument types.
- One does not need to define both
promote_rule(::Type{A}, ::Type{B})
andpromote_rule(::Type{B}, ::Type{A})
— the symmetry is implied by the waypromote_rule
is used in the promotion process. - The
promote_rule
function is used as a building block to define a second function calledpromote_type
, which, given any number of type objects, returns the common type to which those values, as arguments topromote
should be promoted. Thus, if one wants to know, in absence of actual values, what type a collection of values of certain types would promote to, one can usepromote_type
:promote_type(Int8, Uint16)
Modules
- Modules in Julia are separate global variable workspaces. They are delimited syntactically, inside
module Name ... end
. - In a module, the
export MyType, foo
statement makesMyType
andfoo
visible to external modules when theyusing
orimport
the current module. using Lib
using BigLib: thing1, thing2
is same asusing BigLib.thing1, BigLib.thing2
import Base
import BigLib: thing1, thing2
is same asimport BigLib.thing1, BigLib.thing2
importall OtherLib
is same asimport OtherLib.thing1, OtherLib.thing2, .(all the exported vars/funcs/types).
- If only the module name is used or imported, without the
:thing1
or.thing1
, every types, variables and functions are visible throughModuleName.var
notation. - In this case
using
makes exported vars/funcs/types visible without the need of module name, whileimport
doesn’t. - In this case all the module’s functions are extendable, but only by the
ModuleName.fun
notation. - If the module is (partly) used or imported by the
:thing1
or.thing1
notation, or byimportall
, only the explicitly used or imported vars/funcs/types are visible, regardless they are exported or not, without the need of module name, whileModuleName.var
doesn’t work. - In this case,
import
makes the imported functions extendable, whileusing
doesn’t. - Files and file names are mostly unrelated to modules; modules are associated only with module expressions. One can have multiple files per module, and multiple modules per file:
module Foo \n include("file1.jl") \n include("file2.jl") \n end
Main
is the top-level module, and Julia starts withMain
set as the current module. Variables defined at the prompt go in Main, andwhos()
lists variables inMain
.Core
contains all identifiers considered “built in” to the language, i.e. part of the core language and not libraries. Every module implicitly specifiesusing Core
.Base
is the standard library (the contents of base/). All modules implicitly containusing Base
.- Given the statement
using Foo
, the system looks forFoo
withinMain
. If the module does not exist, the system attempts torequire("Foo")
, which typically results in loading code from an installed package. - Modules can have submodules. Using a submodule of the current module:
using .Utils
. - The global variable
LOAD_PATH
contains the directories Julia searches for modules when callingrequire
. - Extend the
LOAD_PATH
bypush!(LOAD_PATH, "/Path/To/My/Module/")
- Putting this statement in the file
~/.juliarc.jl
will extendLOAD_PATH
on every Julia startup. - Alternatively, the module load path can be extended by defining the environment variable
JULIA_LOAD_PATH
.
Multi-dimensional Arrays
- The array library is implemented almost completely in Julia itself, and derives its performance from the compiler, just like any other code written in Julia.*
- In general, unlike many other technical computing languages, Julia does not expect programs to be written in a vectorized style for performance.
- In Julia, all arguments to functions are passed by reference.
- In Julia, modifications made to input arrays within a function will be visible in the parent function.
- The entire Julia array library ensures that inputs are not modified by library functions.
- User code, if it needs to exhibit similar behavior, should take care to create a copy of inputs that it may modify.
Array(type, dims...)
: an uninitialized dense arraycell(dims...)
: an uninitialized cell array (heterogeneous array)zeros(type, dims...)
: an array of all zeros of specified type, defaults toFloat64
if type not specifiedzeros(A)
: an array of all zeros of same element type and shape ofA
ones(type, dims...)
: an array of all ones of specified type, defaults toFloat64
if type not specifiedones(A)
: an array of all ones of same element type and shape ofA
trues(dims...)
: a Bool array with all valuestrue
falses(dims...)
: a Bool array with all valuesfalse
reshape(A, dims...)
: an array with the same data as the given array, but with different dimensions.copy(A)
: copyA
deepcopy(A)
: copyA
, recursively copying its elementssimilar(A, element_type, dims...)
: an uninitialized array of the same type as the given array (dense, sparse, etc.), but with the specified element type and dimensions. The second and third arguments are both optional, defaulting to the element type and dimensions ofA
if omitted.reinterpret(type, A)
: an array with the same binary data as the given array, but with the specified element typerand(dims)
: Array ofFloat64
s with random, and uniformly distributed values in [0,1)randn(dims)
: Array ofFloat64
s with random, and standard normally distributedeye(n)
:n
-by-n
identity matrixeye(m, n)
:m
-by-n
identity matrixlinspace(start, stop, n)
; vector ofn
linearly-spaced elements fromstart
tostop
fill!(A, x)
: fill the arrayA
with the valuex
fill(x, dims)
: create an array filled with the valuex
eltype(A)
: the type of the elements contained inA
length(A)
: the number of elements inA
ndims(A)
: the number of dimensions ofA
size(A)
: a tuple containing the dimensions ofA
size(A,n)
: the size ofA
in a particular dimensionstride(A,k)
: the stride (linear index distance between adjacent elements) along dimensionk
strides(A)
: a tuple of the strides in each dimensioncat(k, A...)
: concatenate inputn
-d arrays along the dimensionk
vcat(A...)
: shorthand forcat(1, A...)
hcat(A...)
: shorthand forcat(2, A...)
[A B C ...]
:hcat()
[A, B, C, ...]
:vcat()
[A B; C D; ...]
:hvcat()
- An array with a specific element type can be constructed using the syntax
T[A, B, C, ...]
- Range:
:
,a:b
,a:b:c
.a=1:3; a.start; a.stop
[1:3]
- Comprehension:
A = [ F(x,y,...) for x=rx, y=ry, ... ]
. The result is an N-d dense array with dimensions that are the concatenation of the dimensions of the variable rangesrx
,ry
, etc. and eachF(x,y,...)
evaluation returns a scalar.in
could be used in place of=
. - The general syntax for indexing into an n-dimensional array
A
is:X = A[I_1, I_2, ..., I_n]
where eachI_k
may be:- A scalar value
- A Range of the form
:
,a:b
, ora:b:c
- An arbitrary integer vector, including the empty vector
[]
- A boolean vector
- Trailing dimensions indexed with scalars are dropped.
- Boolean vectors are first transformed with
find
; the size of a dimension indexed by a boolean vector will be the number of true values in the vector. - Indexing syntax is equivalent to a call to
getindex
:X = getindex(A, I_1, I_2, ..., I_n)
- Empty ranges of the form
n:n-1
are sometimes used to indicate the inter-index location betweenn-1
andn
. - The general syntax for assigning values in an
n
-dimensional arrayA
is:A[I_1, I_2, ..., I_n] = X
where eachI_k
may be:- A scalar value
- A Range of the form
:
,a:b
, ora:b:c
- An arbitrary integer vector, including the empty vector
[]
- A boolean vector
- Index assignment syntax is equivalent to a call to
setindex!()
:setindex!(A, X, I_1, I_2, ..., I_n)
- The following operators are supported for arrays. The dot version of a binary operator should be used for elementwise operations.
- Unary arithmetic —
-
,+
,!
- Binary arithmetic —
+
,-
,*
,.*
,/
,./
,\\
,.\\
,^
,.^
,div
,mod
- Comparison —
.==
,.!=
,.<
,.<=
,.>
,.>=
- Unary Boolean or bitwise —
~
- Binary Boolean or bitwise —
&
,|
,$
- Unary arithmetic —
- Some operators without dots operate elementwise anyway when one argument is a scalar. These operators are
*
,+
,-
, and the bitwise operators. The operators/
and\\
operate elementwise when the denominator is a scalar. - Note that comparisons such as
==
operate on whole arrays, giving a single boolean answer. Use dot operators for elementwise comparisons. - Note that there is a difference between
min()
andmax()
, which operate elementwise over multiple array arguments, andminimum()
andmaximum()
, which find the smallest and largest values within an array. - Julia provides the
@vectorize_1arg()
and@vectorize_2arg()
macros to automatically vectorize any function of one or two arguments respectively. Each of these takes two arguments, namely the Type of argument (which is usually chosen to be the most general possible) and the name of the function to vectorize:square(x) = x^2; @vectorize_1arg Number square; square([1 2 4; 5 6 7])
repmat(a,1,3)+A
- Julia offers
broadcast()
, which expands singleton dimensions in array arguments to match the corresponding dimension in the other array without using extra memory, and applies the given function elementwise:broadcast(+, a, A)
- Elementwise operators such as
.+
and.*
perform broadcasting if necessary. - The base array type in Julia is the abstract type
AbstractArray{T,N}
. It is parametrized by the number of dimensionsN
and the element typeT
. - Operations on
AbstractArray
objects are defined using higher level operators and functions, in a way that is independent of the underlying storage. These operations generally work correctly as a fallback for any specific array implementation. - The
AbstractArray
type includes anything vaguely array-like, and implementations of it might be quite different from conventional arrays. DenseArray
is an abstract subtype ofAbstractArray
intended to include all arrays that are laid out at regular offsets in memory, and which can therefore be passed to external C and Fortran functions expecting this memory layout.- The
Array{T,N}
type is a specific instance ofDenseArray
where elements are stored in column-major order. SubArray
is a specialization ofAbstractArray
that performs indexing by reference rather than by copying.- A
SubArray
is created with thesub()
funtion. The result ofsub()
looks the same as the result ofgetindex()
, except the data is left in place:b = sub(a, 2:2:8,2:2:4)
.
Networking and Streams
write(STDOUT,"Hello World")
read(STDIN,Char)
readbytes(STDIN,4)
readline(STDIN)
- To read every line from
STDIN
you can use theeachline
method:for line in eachline(STDIN)
write(STDOUT,0x61) # a
print(STDOUT,0x61) # 97
f = open("hello.txt")
readlines(f)
f = open("hello.txt","w"); write(f,"Hello again.")
- The IOStream must be closed before the write is actually flushed to disk:
close(f)
- Opening a file, doing something to it’s contents, and closing it again is a very common pattern. To make this easier, there exists another invocation of open which takes a function as it’s first argument and filename as it’s second, opens the file, calls the function with the file as an argument, and then closes it again:
function read_and_capitalize(f::IOStream); open(read_and_capitalize, "hello.txt")
- Or even
open("hello.txt") do f \n uppercase(readall(f)) \n end
Date and DateTime
- The
Dates
module provides two types for working with dates:Date
andDateTime
, representing day and millisecond precision, respectively; both are subtypes of the abstract TimeType. - Both
Date
andDateTime
are basically immutableInt64
wrappers. - The
DateTime
type is timezone-unaware. - Both
Date
andDateTime
are based on the ISO 8601 standard, which follows the proleptic Gregorian calendar. One note is that the ISO 8601 standard is particular about BC/BCE dates. In general, the last day of the BC/BCE era, 1-12-31 BC/BCE, was followed by 1-1-1 AD/CE, thus no year zero exists. Date
andDateTime
types can be constructed by integer orPeriod
types, by parsing, or through adjusters.- Construction using integers: ` DateTime(2013); DateTime(2013,7,1,12); Date(Dates.Year(2013),Dates.Month(7),Dates.Day(1))`
- Parsing and formating string:
dt = DateTime("2014-11-11"); dt = DateTime("20141111", "yyyymmdd"); Dates.format(dt, "yyyy/mm/dd")
- Comparison and difference:
dt > dt2; dt != dt2; dt - dt2
- Query:
Dates.year(t); Dates.week(t); Dates.yearmonth(t); Dates.yearmonthday(t); Date.dayofweek(t); Date.isleapyear(t)
Dates.Year(t)
: returnPeriod
type.- Next/Prev:
const french_daysofweek = Dict(1=>"Lundi",2=>"Mardi", ... ; Dates.VALUETODAYOFWEEK["french"] = french_daysofweek; Dates.dayname(t;locale="french")
- Language:
istuesday = x->Dates.dayofweek(x) == Dates.Tuesday; Dates.tonext(istuesday, Date(2014,7,13))
- Period:
Dates.value(dt2 - dt1)
Nullable Types: Representing Missing Values
- Julia provides a parametric type called
Nullable{T}
, which can be thought of as a specialized container type that can contain either zero or one values. - To construct an object representing a missing value of type
T
, use theNullable{T}()
function:x1 = Nullable{Int}()
- To construct an object representing a non-missing value of type
T
, use theNullable(x::T)
function:x1 = Nullable(1)
- You can check if a
Nullable
object has any value using theisnull
function. - You can safely access the value of an
Nullable
object using theget
function:get(Nullable{Float64}())
. If the value is not present, aNullException
error will be thrown. - In cases for which a reasonable default value exists that could be used when a
Nullable
object’s value turns out to be missing, you can provide this default value as a second argument toget
:get(Nullable{Float64}(), 0)
Interacting With Julia
- When the cursor is at the beginning of the line, the prompt can be changed to a help mode by typing
?
. Julia will attempt to print help or documentation for anything entered in help mode. - In addition to function names, complete function calls may be entered to see which method is called for the given argument(s). Macros, types and variables can also be queried.
- A semicolon (
;
) will enter the shell mode. - To initiate an incremental search through the previous history, type ^R — the control key together with the r key. The prompt will change to (reverse-i-search)‘’:, and as you type the search query will appear in the quotes. The most recent result that matches the query will dynamically update to the right of the colon as more is typed. To find an older result using the same query, simply type ^R again. Just as ^R is a reverse search, ^S is a forward search, with the prompt (i-search)‘’:. The two may be used in conjunction with each other to move through the previous or next matching results, respectively.
Running External Programs
- Julia borrows backtick notation for commands from the shell, Perl, and Ruby:
`echo hello`
- Instead of immediately running the command, backticks create a
Cmd
object to represent the command. You can use this object to connect the command to others via pipes, run it, and read or write to it. - When the command is run, Julia does not capture its output unless you specifically arrange for it to. Instead, the output of the command by default goes to stdout as it would using libc‘s system call.
- The command is never run with a shell. Instead, Julia parses the command syntax directly, appropriately interpolating variables and splitting on words as the shell would, respecting shell quoting syntax. The command is run as julia‘s immediate child process, using fork and exec calls.
run(`echo hello`)
- The
run
method itself returnsnothing
, and throws anErrorException
if the external command fails to run successfully. - If you want to read the output of the external command, the
readall
method can be used instead:a=readall(`echo hello`)
- You can use
$
for interpolation much as you would in a string literal:`sort $file`
- The value of
file
is never interpreted by a shell, so there’s no need for actual quoting. - If you interpolate an array as part of a shell word, Julia emulates the shell’s {a,b,c} argument generation.
- If you interpolate multiple arrays into the same word, the shell’s Cartesian product generation behavior is emulated.
`rm -rf $["foo","bar","baz","qux"].$["aux","log","pdf"]`
- Shell metacharacters, such as
|
,&
, and>
, are not special inside of Julia’s backticks. - One uses Julia’s
|>
operator between Cmd objects:run(`echo hello` |> `sort`)
- Julia itself does the work to setup pipes and connect file descriptors that is normally done by the shell.
- Note that
|>
only redirectsstdout
. To redirectstderr
, use.>
- Julia can run multiple commands in parallel:
run(`echo hello` & `echo world`)
- Julia lets you pipe the output from both of these processes to another program:
run(`echo world` & `echo hello` |> `sort`)
- In terms of UNIX plumbing, what’s happening here is that a single UNIX pipe object is created and written to by both
echo
processes, and the other end of the pipe is read from by thesort
command.
Packages
- All package manager commands are found in the
Pkg
module, included in Julia’s Base install. - The
Pkg.status()
function prints out a summary of the state of packages you have installed. Pkg.installed()
returns a dictionary, mapping installed package names to the version of that package which is installed.- Rather than installing a package, you just add it to the list of requirements and then “resolve” what needs to be installed.
- If some package had been installed because it was needed by a previous version of something you wanted, and a newer version doesn’t have that requirement anymore, updating will actually remove that package.
- Your package requirements are in the file
~/.julia/v0.3/REQUIRE
. You can edit this file by hand and then callPkg.resolve()
to install, upgrade or remove packages to optimally satisfy the requirements, or you can doPkg.edit()
, which will openREQUIRE
in your editor (configured via theEDITOR
orVISUAL
environment variables), and then automatically callPkg.resolve()
afterwards if necessary. - If you only want to add or remove the requirement for a single package, you can also use the non-interactive
Pkg.add
andPkg.rm
commands, which add or remove a single requirement toREQUIRE
and then callPkg.resolve()
- You can add a package to the list of requirements with the
Pkg.add
function, and the package and all the packages that it depends on will be installed. - When you decide that you don’t want to have a package around any more, you can use
Pkg.rm
to remove the requirement for it from theREQUIRE
file. Pkg.edit()
does not roll back the contents ofREQUIRE
ifPkg.resolve()
fails – rather, you have to runPkg.edit()
again to fix the files contents yourself.- Official Julia packages are registered in the
METADATA.jl
repository, available at a well-known location - The package manager can install and work with unregistered packages too. To install an unregistered package, use
Pkg.clone(url)
, whereurl
is a git URL from which the package can be cloned. - By convention, Julia repository names end with .jl.
- If unregistered packages contain a
REQUIRE
file at the top of their source tree, that file will be used to determine which registered packages the unregistered package depends on, and they will automatically be installed. - To get the latest and greatest versions of all your packages, just do
Pkg.update()
.