Scientific computing has traditionally required the highest performance, yet domain experts have largely moved to slower dynamic languages for daily work. We believe there are many good reasons to prefer dynamic languages for these applications, and we do not expect their use to diminish. Fortunately, modern language design and compiler techniques make it possible to mostly eliminate the performance trade-off and provide a single environment productive enough for prototyping and efficient enough for deploying performance-intensive applications. The Julia programming language fills this role: it is a flexible dynamic language, appropriate for scientific and numerical computing, with performance comparable to traditional statically-typed languages.
Julia features optional typing, multiple dispatch, and good performance, achieved using type inference and just-in-time (JIT) compilation, implemented using LLVM. It is multi-paradigm, combining features of imperative, functional, and object-oriented programming. Julia provides ease and expressiveness for high-level numerical computing, in the same way as languages such as R, MATLAB, and Python, but transcends its general programming limitations. To achieve this, Julia builds upon the lineage of mathematical programming languages, but also borrows much from popular dynamic languages, including Lisp, Perl, Python, Lua, and Ruby.
The most significant departures of Julia from typical dynamic languages are:
Although one sometimes speaks of dynamic languages as being “typeless”, they are definitely not: every object, whether primitive or user-defined, has a type. The lack of type declarations in most dynamic languages, however, means that one cannot instruct the compiler about the types of values, and often cannot explicitly talk about types at all. In static languages, on the other hand, while one can — and usually must — annotate types for the compiler, types exist only at compile time and cannot be manipulated or expressed at run time. In Julia, types are themselves run-time objects, and can also be used to convey information to the compiler.
While the casual programmer need not explicitly use types or multiple dispatch, they are the core unifying features of Julia: functions are defined on different combinations of argument types, and applied by dispatching to the most specific matching definition. This model is a good fit for mathematical programming, where it is unnatural for the first argument to “own” an operation as in traditional object-oriented dispatch. Operators are just functions with special notation — to extend addition to new user-defined data types, you define new methods for the + function. Existing code then seamlessly applies to the new data types.
Partly because of run-time type inference (augmented by optional type annotations), and partly because of a strong focus on performance from the inception of the project, Julia’s computational efficiency exceeds that of other dynamic languages, and even rivals that of statically-compiled languages. For large scale numerical problems, speed always has been, continues to be, and probably always will be crucial: the amount of data being processed has easily kept pace with Moore’s Law over the past decades.
Julia aims to create an unprecedented combination of ease-of-use, power, and efficiency in a single language. In addition to the above, some advantages of Julia over comparable systems include:
Julia installation is straightforward, whether using precompiled binaries or compiling from source. Download and install Julia by following the instructions at http://julialang.org/downloads/.
The easiest way to learn and experiment with Julia is by starting an interactive session (also known as a read-eval-print loop or “repl”):
$ julia
_
_ _ _(_)_ |
(_) | (_) (_) | A fresh approach to technical computing.
_ _ _| |_ __ _ |
| | | | | | |/ _` | | Version 0 (pre-release)
| | |_| | | | (_| | | Commit 61847c5aa7 (2011-08-20 06:11:31)*
_/ |\__'_|_|_|\__'_| |
|__/ |
julia> 1 + 2
3
julia> ans
3
To exit the interactive session, type ^D — the control key together with the d key or type quit(). When run in interactive mode, julia displays a banner and prompts the user for input. Once the user has entered a complete expression, such as 1 + 2, and hits enter, the interactive session evaluates the expression and shows its value. If an expression is entered into an interactive session with a trailing semicolon, its value is not shown. The variable ans is bound to the value of the last evaluated expression whether it is shown or not. The ans variable is only bound in interactive sessions, not when Julia code is run in other ways.
To evaluate expressions written in a source file file.jl, write include("file.jl").
To run code in a file non-interactively, you can give it as the first argument to the julia command:
$ julia script.jl arg1 arg2...
As the example implies, the following command-line arguments to julia are taken as command-line arguments to the program script.jl, passed in the global constant ARGS. ARGS is also set when script code is given using the -e option on the command line (see the julia help output below). For example, to just print the arguments given to a script, you could do this:
$ julia -e 'for x in ARGS; println(x); end' foo bar
foo
bar
Or you could put that code into a script and run it:
$ echo 'for x in ARGS; println(x); end' > script.jl
$ julia script.jl foo bar
foo
bar
Julia can be started in parallel mode with either the -p or the --machinefile options. -p n will launch an additional n worker processes, while --machinefile file will launch a worker for each line in file file. The machines defined in file must be accessible via ssh and each machine definition takes the form [user@]host[:port]
If you have code that you want executed whenever julia is run, you can put it in ~\.juliarc.jl:
$ echo 'println("Greetings! 你好! 안녕하세요?")' > ~/.juliarc.jl
$ julia
Greetings! 你好! 안녕하세요?
...
There are various ways to run Julia code and provide options, similar to those available for the perl and ruby programs:
julia [options] [program] [args...]
-v --version Display version information
-q --quiet Quiet startup without banner
-H --home=<dir> Load files relative to <dir>
-T --tab=<size> Set REPL tab width to <size>
-e --eval=<expr> Evaluate <expr>
-E --print=<expr> Evaluate and show <expr>
-P --post-boot=<expr> Evaluate <expr> right after boot
-L --load=file Load <file> right after boot on all processors
-J --sysimage=file Start up with the given system image file
-p n Run n local processes
--machinefile file Run processes on hosts listed in file
--no-history Don't load or save history
-f --no-startup Don't load ~/.juliarc.jl
-F Load ~/.juliarc.jl, then handle remaining inputs
--color=yes|no Enable or disable color text
-h --help Print this message
In addition to this manual, there are various other resources that may help new users get started with julia:
Julia provides an extremely flexible system for naming variables. Variable names are case-sensitive, and have no semantic meaning (that is, the language will not treat variables differently based on their names).
julia> ix = 1.0
1.0
julia> y = -3
-3
julia> Z = "My string"
"My string"
julia> customary_phrase = "Hello world!"
"Hello world!"
julia> UniversalDeclarationOfHumanRightsStart = "人人生而自由，在尊严和权力上一律平等。"
"人人生而自由，在尊严和权力上一律平等。"
Unicode names (in UTF-8 encoding) are allowed:
julia> δ = 0.00001
0.00001
julia> 안녕하세요 = "Hello"
"Hello"
Julia will even let you redefine built-in constants and functions if needed:
julia> pi
π = 3.1415926535897...
julia> pi = 3
Warning: imported binding for pi overwritten in module Main
3
julia> pi
3
julia> sqrt = 4
4
However, this is obviously not recommended to avoid potential confusion.
Variable names must begin with a letter (A-Z or a-z), underscore, or Unicode character with code point greater than 00A0. Subsequent characters may also include ! and digits (0-9).
All operators are also valid identifiers, but are parsed specially. In some contexts operators can be used just like variables; for example (+) refers to the addition function, and (+) = f will reassign it.
The only explicitly disallowed names for variables are the names of built-in statements:
julia> else = false
ERROR: syntax: unexpected else
julia> try = "No"
ERROR: syntax: unexpected =
While Julia imposes few restrictions on valid names, it has become useful to adopt the following conventions:
Integers and floating-point values are the basic building blocks of arithmetic and computation. Built-in representations of such values are called numeric primitives, while representations of integers and floating-point numbers as immediate values in code are known as numeric literals. For example, 1 is an integer literal, while 1.0 is a floating-point literal; their binary in-memory representations as objects are numeric primitives.
Julia provides a broad range of primitive numeric types, and a full complement of arithmetic and bitwise operators as well as standard mathematical functions are defined over them. These map directly onto numeric types and operations that are natively supported on modern computers, thus allowing Julia to take full advantage of computational resources. Additionally, Julia provides software support for Arbitrary Precision Arithmetic, which can handle operations on numeric values that cannot be represented effectively in native hardware representations, but at the cost of relatively slower performance.
The following are Julia’s primitive numeric types:
Type | Signed? | Number of bits | Smallest value | Largest value |
Int8 | ✓ | 8 | -2^7 | 2^7 - 1 |
Uint8 | 8 | 0 | 2^8 - 1 | |
Int16 | ✓ | 16 | -2^15 | 2^15 - 1 |
Uint16 | 16 | 0 | 2^16 - 1 | |
Int32 | ✓ | 32 | -2^31 | 2^31 - 1 |
Uint32 | 32 | 0 | 2^32 - 1 | |
Int64 | ✓ | 64 | -2^63 | 2^63 - 1 |
Uint64 | 64 | 0 | 2^64 - 1 | |
Int128 | ✓ | 128 | -2^127 | 2^127 - 1 |
Uint128 | 128 | 0 | 2^128 - 1 | |
Bool | N/A | 8 | false (0) | true (1) |
Char | N/A | 32 | '\0' | '\Uffffffff' |
Char natively supports representation of Unicode characters; see Strings for more details.
Type | Precision | Number of bits |
Float16 | half | 16 |
Float32 | single | 32 |
Float64 | double | 64 |
Additionally, full support for Complex and Rational Numbers is built on top of these primitive numeric types. All numeric types interoperate naturally without explicit casting, thanks to a flexible, user-extensible type promotion system.
Literal integers are represented in the standard manner:
julia> 1
1
julia> 1234
1234
The default type for an integer literal depends on whether the target system has a 32-bit architecture or a 64-bit architecture:
# 32-bit system:
julia> typeof(1)
Int32
# 64-bit system:
julia> typeof(1)
Int64
The Julia internal variable WORD_SIZE indicates whether the target system is 32-bit or 64-bit.:
# 32-bit system:
julia> WORD_SIZE
32
# 64-bit system:
julia> WORD_SIZE
64
Julia also defines the types Int and UInt, which are aliases for the system’s signed and unsigned native integer types respectively.:
# 32-bit system:
julia> Int
Int32
julia> Uint
Uint32
# 64-bit system:
julia> Int
Int64
julia> Uint
Uint64
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:
# 32-bit or 64-bit system:
julia> typeof(3000000000)
Int64
Unsigned integers are input and output using the 0x prefix and hexadecimal (base 16) digits 0-9a-f (the capitalized digits A-F also work for input). The size of the unsigned value is determined by the number of hex digits used:
julia> 0x1
0x01
julia> typeof(ans)
Uint8
julia> 0x123
0x0123
julia> typeof(ans)
Uint16
julia> 0x1234567
0x01234567
julia> typeof(ans)
Uint32
julia> 0x123456789abcdef
0x0123456789abcdef
julia> typeof(ans)
Uint64
This behavior is based on the observation that when one uses unsigned hex literals for integer values, one typically is using them to represent a fixed numeric byte sequence, rather than just an integer value.
Recall that the variable ans is set to the value of the last expression evaluated in an interactive session. This does not occur when Julia code is run in other ways.
Binary and octal literals are also supported:
julia> 0b10
0x02
julia> typeof(ans)
Uint8
julia> 0o10
0x08
julia> typeof(ans)
Uint8
The minimum and maximum representable values of primitive numeric types such as integers are given by the typemin and typemax functions:
julia> (typemin(Int32), typemax(Int32))
(-2147483648,2147483647)
julia> for T = {Int8,Int16,Int32,Int64,Int128,Uint8,Uint16,Uint32,Uint64,Uint128}
println("$(lpad(T,6)): [$(typemin(T)),$(typemax(T))]")
end
Int8: [-128,127]
Int16: [-32768,32767]
Int32: [-2147483648,2147483647]
Int64: [-9223372036854775808,9223372036854775807]
Int128: [-170141183460469231731687303715884105728,170141183460469231731687303715884105727]
Uint8: [0x00,0xff]
Uint16: [0x0000,0xffff]
Uint32: [0x00000000,0xffffffff]
Uint64: [0x0000000000000000,0xffffffffffffffff]
Uint128: [0x00000000000000000000000000000000,0xffffffffffffffffffffffffffffffff]
The values returned by typemin and typemax are always of the given argument type. (The above expression uses several features we have yet to introduce, including for loops, Strings, and Interpolation, but should be easy enough to understand for users with some existing programming experience.)
In Julia, exceeding the maximum representable value of a given type results in a wraparound behavior:
julia> x = typemax(Int64)
9223372036854775807
julia> x + 1
-9223372036854775808
julia> x + 1 == typemin(Int64)
true
Thus, arithmetic with Julia integers is actually a form of modular arithmetic. This reflects the characteristics of the underlying arithmetic of integers as implemented on modern computers. In applications where overflow is possible, explicit checking for wraparound produced by overflow is essential; otherwise, the BigInt type in Arbitrary Precision Arithmetic is recommended instead.
Literal floating-point numbers are represented in the standard formats:
julia> 1.0
1.0
julia> 1.
1.0
julia> 0.5
0.5
julia> .5
0.5
julia> -1.23
-1.23
julia> 1e10
1e+10
julia> 2.5e-4
0.00025
The above results are all Float64 values. Literal Float32 values can be entered by writing an f in place of e:
julia> 0.5f0
0.5f0
julia> typeof(ans)
Float32
julia> 2.5f-4
0.00025f0
Values can be converted to Float32 easily:
julia> float32(-1.5)
-1.5f0
julia> typeof(ans)
Float32
Hexadecimal floating-point literals are also valid, but only as Float64 values:
julia> 0x1p0
1.0
julia> 0x1.8p3
12.0
julia> 0x.4p-1
0.125
julia> typeof(ans)
Float64
Half-precision floating-point numbers are also supported (Float16), but only as a storage format. In calculations they’ll be converted to Float32:
julia> sizeof(float16(4.))
2
julia> 2*float16(4.)
8.0f0
Floating-point numbers have two zeros, positive zero and negative zero. They are equal to each other but have different binary representations, as can be seen using the bits function:
julia> 0.0 == -0.0
true
julia> bits(0.0)
"0000000000000000000000000000000000000000000000000000000000000000"
julia> bits(-0.0)
"1000000000000000000000000000000000000000000000000000000000000000"
There are three specified standard floating-point values that do not correspond to any point on the real number line:
Special value | Name | Description | ||
---|---|---|---|---|
Float16 | Float32 | Float64 | ||
Inf16 | Inf32 | Inf | positive infinity | a value greater than all finite floating-point values |
-Inf16 | -Inf32 | -Inf | negative infinity | a value less than all finite floating-point values |
NaN16 | NaN32 | NaN | not a number | a value not == to any floating-point value (including itself) |
For further discussion of how these non-finite floating-point values are ordered with respect to each other and other floats, see Numeric Comparisons. By the IEEE 754 standard, these floating-point values are the results of certain arithmetic operations:
julia> 1/Inf
0.0
julia> 1/0
Inf
julia> -5/0
-Inf
julia> 0.000001/0
Inf
julia> 0/0
NaN
julia> 500 + Inf
Inf
julia> 500 - Inf
-Inf
julia> Inf + Inf
Inf
julia> Inf - Inf
NaN
julia> Inf * Inf
Inf
julia> Inf / Inf
NaN
julia> 0 * Inf
NaN
The typemin and typemax functions also apply to floating-point types:
julia> (typemin(Float16),typemax(Float16))
(Float16(0xfc00),Float16(0x7c00))
julia> (typemin(Float32),typemax(Float32))
(-Inf32,Inf32)
julia> (typemin(Float64),typemax(Float64))
(-Inf,Inf)
Most real numbers cannot be represented exactly with floating-point numbers, and so for many purposes it is important to know the distance between two adjacent representable floating-point numbers, which is often known as machine epsilon.
Julia provides the eps function, which gives the distance between 1.0 and the next larger representable floating-point value:
julia> eps(Float32)
1.192092896e-07
julia> eps(Float64)
2.22044604925031308e-16
julia> eps() #Same as eps(Float64)
2.22044604925031308e-16
These values are 2.0^-23 and 2.0^-52 as Float32 and Float64 values, respectively. 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 as x such that x + eps(x) is the next representable floating-point value larger than x:
julia> eps(1.0)
2.22044604925031308e-16
julia> eps(1000.)
1.13686837721616030e-13
julia> eps(1e-27)
1.79366203433576585e-43
julia> eps(0.0)
5.0e-324
The distance between two adjacent representable floating-point numbers is not constant, but is smaller for smaller values and larger for larger values. In other words, the representable floating-point numbers are densest in the real number line near zero, and grow sparser exponentially as one moves farther away from zero. By definition, eps(1.0) is the same as eps(Float64) since 1.0 is a 64-bit floating-point value.
Julia also provides the nextfloat and prevfloat functions which return the next largest or smallest representable floating-point number to the argument respectively:
julia> x = 1.25f0
1.25f0
julia> nextfloat(x)
1.2500001f0
julia> prevfloat(x)
1.2499999f0
julia> bits(prevfloat(x))
"00111111100111111111111111111111"
julia> bits(x)
"00111111101000000000000000000000"
julia> bits(nextfloat(x))
"00111111101000000000000000000001"
This example highlights the general principle that the adjacent representable floating-point numbers also have adjacent binary integer representations.
If a number doesn’t have an exact floating-point representation, it must be rounded to an appropriate representable value, however, if wanted, the manner in which this rounding is done can be changed according to the rounding modes presented in the IEEE 754 standard:
julia> 1.1 + 0.1
1.2000000000000002
julia> with_rounding(RoundDown) do
1.1 + 0.1
end
1.2
The default mode used is always RoundNearest, which rounds to the nearest representable value, with ties rounded towards the nearest value with an even least significant bit.
Floating-point arithmetic entails many subtleties which can be surprising to users who are unfamiliar with the low-level implementation details. However, these subtleties are described in detail in most books on scientific computation, and also in the following references:
To allow computations with arbitrary-precision integers and floating point numbers, Julia wraps the GNU Multiple Precision Arithmetic Library (GMP) and the GNU MPFR Library, respectively. The BigInt and BigFloat types are available in Julia for arbitrary precision integer and floating point numbers respectively.
Constructors exist to create these types from primitive numerical types, or from String. Once created, they participate in arithmetic with all other numeric types thanks to Julia’s type promotion and conversion mechanism.
julia> BigInt(typemax(Int64)) + 1
9223372036854775808
julia> BigInt("123456789012345678901234567890") + 1
123456789012345678901234567891
julia> BigFloat("1.23456789012345678901")
1.234567890123456789010000000000000000000000000000000000000000000000000000000004e+00 with 256 bits of precision
julia> BigFloat(2.0^66) / 3
2.459565876494606882133333333333333333333333333333333333333333333333333333333344e+19 with 256 bits of precision
julia> factorial(BigInt(40))
815915283247897734345611269596115894272000000000
However, type promotion between the primitive types above and BigInt/BigFloat is not automatic and must be explicitly stated.
julia> x = typemin(Int64)
-9223372036854775808
julia> x = x - 1
9223372036854775807
julia> typeof(x)
Int64
julia> y = BigInt(typemin(Int64))
-9223372036854775808
julia> y = y - 1
-9223372036854775809
julia> typeof(y)
BigInt
The default precision (in number of bits of the significand) and rounding mode of BigFloat operations can be changed, and all further calculations will take these changes in account:
julia> with_bigfloat_rounding(RoundUp) do
BigFloat(1) + BigFloat("0.1")
end
1.100000000000000000000000000000000000000000000000000000000000000000000000000003e+00 with 256 bits of precision
julia> with_bigfloat_rounding(RoundDown) do
BigFloat(1) + BigFloat("0.1")
end
1.099999999999999999999999999999999999999999999999999999999999999999999999999986e+00 with 256 bits of precision
julia> with_bigfloat_precision(40) do
BigFloat(1) + BigFloat("0.1")
end
1.0999999999985e+00 with 40 bits of precision
To make common numeric formulas and expressions clearer, Julia allows variables to be immediately preceded by a numeric literal, implying multiplication. This makes writing polynomial expressions much cleaner:
julia> x = 3
3
julia> 2x^2 - 3x + 1
10
julia> 1.5x^2 - .5x + 1
13.0
It also makes writing exponential functions more elegant:
julia> 2^2x
64
The precedence of numeric literal coefficients is the same as that of unary operators such as negation. So 2^3x is parsed as 2^(3x), and 2x^3 is parsed as 2*(x^3).
Numeric literals also work as coefficients to parenthesized expressions:
julia> 2(x-1)^2 - 3(x-1) + 1
3
Additionally, parenthesized expressions can be used as coefficients to variables, implying multiplication of the expression by the variable:
julia> (x-1)x
6
Neither juxtaposition of two parenthesized expressions, nor placing a variable before a parenthesized expression, however, can be used to imply multiplication:
julia> (x-1)(x+1)
type error: apply: expected Function, got Int64
julia> x(x+1)
type error: apply: expected Function, got Int64
Both of these expressions are interpreted as function application: any expression that is not a numeric literal, when immediately followed by a parenthetical, is interpreted as a function applied to the values in parentheses (see Functions for more about functions). Thus, in both of these cases, an error occurs since the left-hand value is not a function.
The above syntactic enhancements significantly reduce the visual noise incurred when writing common mathematical formulae. Note that no whitespace may come between a numeric literal coefficient and the identifier or parenthesized expression which it multiplies.
Juxtaposed literal coefficient syntax may conflict with two numeric literal syntaxes: hexadecimal integer literals and engineering notation for floating-point literals. Here are some situations where syntactic conflicts arise:
In both cases, we resolve the ambiguity in favor of interpretation as a numeric literals:
Julia provides functions which return literal 0 and 1 corresponding to a specified type or the type of a given variable.
Function | Description |
zero(x) | Literal zero of type x or type of variable x |
one(x) | Literal one of type x or type of variable x |
These functions are useful in Numeric Comparisons to avoid overhead from unnecessary type conversion.
Examples:
julia> zero(Float32)
0.0f0
julia> zero(1.0)
0.0
julia> one(Int32)
1
julia> one(BigFloat)
1e+00
Julia provides a complete collection of basic arithmetic and bitwise operators across all of its numeric primitive types, as well as providing portable, efficient implementations of a comprehensive collection of standard mathematical functions.
The following arithmetic operators are supported on all primitive numeric types:
Expression | Name | Description |
---|---|---|
+x | unary plus | the identity operation |
-x | unary minus | maps values to their additive inverses |
x + y | binary plus | performs addition |
x - y | binary minus | performs subtraction |
x * y | times | performs multiplication |
x / y | divide | performs division |
x \ y | inverse divide | equivalent to y / x |
x ^ y | power | raises x to the yth power |
x % y | remainder | equivalent to rem(x,y) |
as well as the negation on Bool types:
Expression | Name | Description |
---|---|---|
!x | negation | changes true to false and vice versa |
Julia’s promotion system makes arithmetic operations on mixtures of argument types “just work” naturally and automatically. See Conversion and Promotion for details of the promotion system.
Here are some simple examples using arithmetic operators:
julia> 1 + 2 + 3
6
julia> 1 - 2
-1
julia> 3*2/12
0.5
(By convention, we tend to space less tightly binding operators less tightly, but there are no syntactic constraints.)
The following bitwise operators are supported on all primitive integer types:
Expression | Name |
---|---|
~x | bitwise not |
x & y | bitwise and |
x | y | bitwise or |
x $ y | bitwise xor (exclusive or) |
x >>> y | logical shift right |
x >> y | arithmetic shift right |
x << y | logical/arithmetic shift left |
Here are some examples with bitwise operators:
julia> ~123
-124
julia> 123 & 234
106
julia> 123 | 234
251
julia> 123 $ 234
145
julia> ~uint32(123)
0xffffff84
julia> ~uint8(123)
0x84
Every binary arithmetic and bitwise operator also has an updating version that assigns the result of the operation back into its left operand. The updating version of the binary operator is formed by placing a = immediately after the operator. For example, writing x += 3 is equivalent to writing x = x + 3:
julia> x = 1
1
julia> x += 3
4
julia> x
4
The updating versions of all the binary arithmetic and bitwise operators are:
+= -= *= /= \= %= ^= &= |= $= >>>= >>= <<=
Standard comparison operations are defined for all the primitive numeric types:
Operator | Name |
---|---|
== | equality |
!= | inequality |
< | less than |
<= | less than or equal to |
> | greater than |
>= | greater than or equal to |
Here are some simple examples:
julia> 1 == 1
true
julia> 1 == 2
false
julia> 1 != 2
true
julia> 1 == 1.0
true
julia> 1 < 2
true
julia> 1.0 > 3
false
julia> 1 >= 1.0
true
julia> -1 <= 1
true
julia> -1 <= -1
true
julia> -1 <= -2
false
julia> 3 < -0.5
false
Integers are compared in the standard manner — by comparison of bits. Floating-point numbers are compared according to the IEEE 754 standard:
The last point is potentially surprising and thus worth noting:
julia> NaN == NaN
false
julia> NaN != NaN
true
julia> NaN < NaN
false
julia> NaN > NaN
false
and can cause especial headaches with Arrays:
julia> [1 NaN] == [1 NaN]
false
Julia provides additional functions to test numbers for special values, which can be useful in situations like hash key comparisons:
Function | Tests if |
---|---|
isequal(x, y) | x and y are identical |
isfinite(x) | x is a finite number |
isinf(x) | x is infinite |
isnan(x) | x is not a number |
isequal considers NaNs equal to each other:
julia> isequal(NaN,NaN)
true
julia> isequal([1 NaN], [1 NaN])
true
julia> isequal(NaN,NaN32)
false
isequal can also be used to distinguish signed zeros:
julia> -0.0 == 0.0
true
julia> isequal(-0.0, 0.0)
false
Mixed-type comparisons between signed integers, unsigned integers, and floats can be very tricky. A great deal of care has been taken to ensure that Julia does them correctly.
Unlike most languages, with the notable exception of Python, comparisons can be arbitrarily chained:
julia> 1 < 2 <= 2 < 3 == 3 > 2 >= 1 == 1 < 3 != 5
true
Chaining comparisons is often quite convenient in numerical code. Chained comparisons use the && operator for scalar comparisons, and the & operator for element-wise comparisons, which allows them to work on arrays. For example, 0 .< A .< 1 gives a boolean array whose entries are true where the corresponding elements of A are between 0 and 1.
Note the evaluation behavior of chained comparisons:
v(x) = (println(x); x)
julia> v(1) < v(2) <= v(3)
2
1
3
true
julia> v(1) > v(2) <= v(3)
2
1
false
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). However, 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. If side effects are required, the short-circuit && operator should be used explicitly (see Short-Circuit Evaluation).
Julia provides a comprehensive collection of mathematical functions and operators. These mathematical operations are defined over as broad a class of numerical values as permit sensible definitions, including integers, floating-point numbers, rationals, and complexes, wherever such definitions make sense.
Function | Description | Return type |
---|---|---|
round(x) | round x to the nearest integer | FloatingPoint |
iround(x) | round x to the nearest integer | Integer |
floor(x) | round x towards -Inf | FloatingPoint |
ifloor(x) | round x towards -Inf | Integer |
ceil(x) | round x towards +Inf | FloatingPoint |
iceil(x) | round x towards +Inf | Integer |
trunc(x) | round x towards zero | FloatingPoint |
itrunc(x) | round x towards zero | Integer |
Function | Description |
---|---|
div(x,y) | truncated division; quotient rounded towards zero |
fld(x,y) | floored division; quotient rounded towards -Inf |
rem(x,y) | remainder; satisfies x == div(x,y)*y + rem(x,y); sign matches x |
mod(x,y) | modulus; satisfies x == fld(x,y)*y + mod(x,y); sign matches y |
gcd(x,y...) | greatest common divisor of x, y,...; sign matches x |
lcm(x,y...) | least common multiple of x, y,...; sign matches x |
Function | Description |
---|---|
abs(x) | a positive value with the magnitude of x |
abs2(x) | the squared magnitude of x |
sign(x) | indicates the sign of x, returning -1, 0, or +1 |
signbit(x) | indicates whether the sign bit is on (1) or off (0) |
copysign(x,y) | a value with the magnitude of x and the sign of y |
flipsign(x,y) | a value with the magnitude of x and the sign of x*y |
Function | Description |
---|---|
sqrt(x) | the square root of x |
cbrt(x) | the cube root of x |
hypot(x,y) | hypotenuse of right-angled triangle with other sides of length x and y |
exp(x) | the natural exponential function at x |
expm1(x) | accurate exp(x)-1 for x near zero |
ldexp(x,n) | x*2^n computed efficiently for integer values of n |
log(x) | the natural logarithm of x |
log(b,x) | the base b logarithm of x |
log2(x) | the base 2 logarithm of x |
log10(x) | the base 10 logarithm of x |
log1p(x) | accurate log(1+x) for x near zero |
exponent(x) | returns the binary exponent of x |
significand(x) | returns the binary significand (a.k.a. mantissa) of a floating-point number x |
For an overview of why functions like hypot, expm1, and log1p are necessary and useful, see John D. Cook’s excellent pair of blog posts on the subject: expm1, log1p, erfc, and hypot.
All the standard trigonometric and hyperbolic functions are also defined:
sin cos tan cot sec csc
sinh cosh tanh coth sech csch
asin acos atan acot asec acsc
asinh acosh atanh acoth asech acsch
sinc cosc atan2
These are all single-argument functions, with the exception of atan2, which gives the angle in radians between the x-axis and the point specified by its arguments, interpreted as x and y coordinates.
In order to compute trigonometric functions with degrees instead of radians, suffix the function with d. For example, sind(x) computes the sine of x where x is specified in degrees. The complete list of trigonometric functions with degree variants is:
sind cosd tand cotd secd cscd
asind acosd atand acotd asecd acscd
Function | Description |
---|---|
erf(x) | the error function at x |
erfc(x) | the complementary error function, i.e. the accurate version of 1-erf(x) for large x |
erfinv(x) | the inverse function to erf |
erfcinv(x) | the inverse function to erfc |
erfi(x) | the imaginary error function defined as -im * erf(x * im), where im is the imaginary unit |
erfcx(x) | the scaled complementary error function, i.e. accurate exp(x^2) * erfc(x) for large x |
dawson(x) | the scaled imaginary error function, a.k.a. Dawson function, i.e. accurate exp(-x^2) * erfi(x) * sqrt(pi) / 2 for large x |
gamma(x) | the gamma function at x |
lgamma(x) | accurate log(gamma(x)) for large x |
lfact(x) | accurate log(factorial(x)) for large x; same as lgamma(x+1) for x > 1, zero otherwise |
digamma(x) | the digamma function (i.e. the derivative of lgamma) at x |
beta(x,y) | the beta function at x,y |
lbeta(x,y) | accurate log(beta(x,y)) for large x or y |
eta(x) | the Dirichlet eta function at x |
zeta(x) | the Riemann zeta function at x |
airy(x), airyai(x) | the Airy Ai function at x |
airyprime(x), airyaiprime(x) | the derivative of the Airy Ai function at x |
airybi(x) | the Airy Bi function at x |
airybiprime(x) | the derivative of the Airy Bi function at x |
airy(k,x) | the k-th derivative of the Airy Ai function at x |
besselj(nu,z) | the Bessel function of the first kind of order nu at z |
besselj0(z) | besselj(0,z) |
besselj1(z) | besselj(1,z) |
bessely(nu,z) | the Bessel function of the second kind of order nu at z |
bessely0(z) | bessely(0,z) |
bessely1(z) | bessely(1,z) |
besselh(nu,k,z) | the Bessel function of the third kind (a.k.a. Hankel function) of order nu at z; k must be either 1 or 2 |
hankelh1(nu,z) | besselh(nu, 1, z) |
hankelh2(nu,z) | besselh(nu, 2, z) |
besseli(nu,z) | the modified Bessel function of the first kind of order nu at z |
besselk(nu,z) | the modified Bessel function of the second kind of order nu at z |
Julia ships with predefined types representing both complex and rational numbers, and supports all standard mathematical operations on them. Conversion and Promotion are defined so that operations on any combination of predefined numeric types, whether primitive or composite, behave as expected.
The global constant im is bound to the complex number i, representing the principal square root of -1. It was deemed harmful to co-opt the name i for a global constant, since it is such a popular index variable name. Since Julia allows numeric literals to be juxtaposed with identifiers as coefficients, this binding suffices to provide convenient syntax for complex numbers, similar to the traditional mathematical notation:
julia> 1 + 2im
1 + 2im
You can perform all the standard arithmetic operations with complex numbers:
julia> (1 + 2im)*(2 - 3im)
8 + 1im
julia> (1 + 2im)/(1 - 2im)
-0.6 + 0.8im
julia> (1 + 2im) + (1 - 2im)
2 + 0im
julia> (-3 + 2im) - (5 - 1im)
-8 + 3im
julia> (-1 + 2im)^2
-3 - 4im
julia> (-1 + 2im)^2.5
2.729624464784009 - 6.9606644595719im
julia> (-1 + 2im)^(1 + 1im)
-0.27910381075826657 + 0.08708053414102428im
julia> 3(2 - 5im)
6 - 15im
julia> 3(2 - 5im)^2
-63 - 60im
julia> 3(2 - 5im)^-1.0
0.20689655172413793 + 0.5172413793103449im
The promotion mechanism ensures that combinations of operands of different types just work:
julia> 2(1 - 1im)
2 - 2im
julia> (2 + 3im) - 1
1 + 3im
julia> (1 + 2im) + 0.5
1.5 + 2.0im
julia> (2 + 3im) - 0.5im
2.0 + 2.5im
julia> 0.75(1 + 2im)
0.75 + 1.5im
julia> (2 + 3im) / 2
1.0 + 1.5im
julia> (1 - 3im) / (2 + 2im)
-0.5 - 1.0im
julia> 2im^2
-2 + 0im
julia> 1 + 3/4im
1.0 - 0.75im
Note that 3/4im == 3/(4*im) == -(3/4*im), since a literal coefficient binds more tightly than division.
Standard functions to manipulate complex values are provided:
julia> real(1 + 2im)
1
julia> imag(1 + 2im)
2
julia> conj(1 + 2im)
1 - 2im
julia> abs(1 + 2im)
2.23606797749979
julia> abs2(1 + 2im)
5
As is common, the absolute value of a complex number is its distance from zero. The abs2 function gives the square of the absolute value, and is of particular use for complex numbers, where it avoids taking a square root. The full gamut of other Elementary Functions is also defined for complex numbers:
julia> sqrt(im)
0.7071067811865476 + 0.7071067811865475im
julia> sqrt(1 + 2im)
1.272019649514069 + 0.7861513777574233im
julia> cos(1 + 2im)
2.0327230070196656 - 3.0518977991517997im
julia> exp(1 + 2im)
-1.1312043837568138 + 2.471726672004819im
julia> sinh(1 + 2im)
-0.48905625904129374 + 1.4031192506220407im
Note that mathematical functions typically return real values when applied to real numbers and complex values when applied to complex numbers. For example, sqrt, for example, behaves differently when applied to -1 versus -1 + 0im even though -1 == -1 + 0im:
julia> sqrt(-1)
ERROR: DomainError()
in sqrt at math.jl:111
julia> sqrt(-1 + 0im)
0.0 + 1.0im
The literal numeric coefficient notation does work when constructing complex number from variables. Instead, the multiplication must be explicitly written out:
julia> a = 1; b = 2; a + b*im
1 + 2im
Hoever, this is not recommended; Use the complex function instead to construct a complex value directly from its real and imaginary parts.:
julia> complex(a,b)
1 + 2im
This construction avoids the multiplication and addition operations.
Inf and NaN propagate through complex numbers in the real and imaginary parts of a complex number as described in the Special floating-point values section:
julia> 1 + Inf*im
complex(1.0,Inf)
julia> 1 + NaN*im
complex(1.0,NaN)
Julia has a rational number type to represent exact ratios of integers. Rationals are constructed using the // operator:
julia> 2//3
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:
julia> 6//9
2//3
julia> -4//8
-1//2
julia> 5//-15
-1//3
julia> -4//-12
1//3
This normalized form for a ratio of integers is unique, so equality of rational values can be tested by checking for equality of the numerator and denominator. The standardized numerator and denominator of a rational value can be extracted using the num and den functions:
julia> num(2//3)
2
julia> den(2//3)
3
Direct comparison of the numerator and denominator is generally not necessary, since the standard arithmetic and comparison operations are defined for rational values:
julia> 2//3 == 6//9
true
julia> 2//3 == 9//27
false
julia> 3//7 < 1//2
true
julia> 3//4 > 2//3
true
julia> 2//4 + 1//6
2//3
julia> 5//12 - 1//4
1//6
julia> 5//8 * 3//12
5//32
julia> 6//5 / 10//7
21//25
Rationals can be easily converted to floating-point numbers:
julia> float(3//4)
0.75
Conversion from rational to floating-point respects the following identity for any integral values of a and b, with the exception of the case a == 0 and b == 0:
julia> isequal(float(a//b), a/b)
true
Constructing infinite rational values is acceptable:
julia> 5//0
Inf
julia> -3//0
-Inf
julia> typeof(ans)
Rational{Int64}
Trying to construct a NaN rational value, however, is not:
julia> 0//0
invalid rational: 0//0
As usual, the promotion system makes interactions with other numeric types effortless:
julia> 3//5 + 1
8//5
julia> 3//5 - 0.5
0.1
julia> 2//7 * (1 + 2im)
2//7 + 4//7im
julia> 2//7 * (1.5 + 2im)
0.42857142857142855 + 0.5714285714285714im
julia> 3//2 / (1 + 2im)
3//10 - 3//5im
julia> 1//2 + 2im
1//2 + 2//1im
julia> 1 + 2//3im
1//1 + 2//3im
julia> 0.5 == 1//2
true
julia> 0.33 == 1//3
false
julia> 0.33 < 1//3
true
julia> 1//3 - 0.33
0.0033333333333332993
Strings are finite sequences of characters. Of course, the real trouble comes when one asks what a character is. The characters that English speakers are familiar with are the letters A, B, C, etc., together with numerals and common punctuation symbols. These characters are standardized together with a mapping to integer values between 0 and 127 by the ASCII standard. There are, of course, many other characters used in non-English languages, including variants of the ASCII characters with accents and other modifications, related scripts such as Cyrillic and Greek, and scripts completely unrelated to ASCII and English, including Arabic, Chinese, Hebrew, Hindi, Japanese, and Korean. The Unicode standard tackles the complexities of what exactly a character is, and is generally accepted as the definitive standard addressing this problem. Depending on your needs, you can either ignore these complexities entirely and just pretend that only ASCII characters exist, or you can write code that can handle any of the characters or encodings that one may encounter when handling non-ASCII text. Julia makes dealing with plain ASCII text simple and efficient, and handling Unicode is as simple and efficient as possible. In particular, you can write C-style string code to process ASCII strings, and they will work as expected, both in terms of performance and semantics. If such code encounters non-ASCII text, it will gracefully fail with a clear error message, rather than silently introducing corrupt results. When this happens, modifying the code to handle non-ASCII data is straightforward.
There are a few noteworthy high-level features about Julia’s strings:
A Char value represents a single character: it is just a 32-bit integer with a special literal representation and appropriate arithmetic behaviors, whose numeric value is interpreted as a Unicode code point. Here is how Char values are input and shown:
julia> 'x'
'x'
julia> typeof(ans)
Char
You can convert a Char to its integer value, i.e. code point, easily:
julia> int('x')
120
julia> typeof(ans)
Int64
On 32-bit architectures, typeof(ans) will be Int32. You can convert an integer value back to a Char just as easily:
julia> char(120)
'x'
Not all integer values are valid Unicode code points, but 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:
julia> char(0x110000)
'\U110000'
julia> is_valid_char(0x110000)
false
As of this writing, the valid Unicode code points are U+00 through U+d7ff and U+e000 through U+10ffff. These have not all been assigned intelligible meanings yet, nor are they necessarily interpretable by applications, but all of these values are considered to be valid Unicode characters.
You can input any Unicode character in single quotes using \u followed by up to four hexadecimal digits or \U followed by up to eight hexadecimal digits (the longest valid value only requires six):
julia> '\u0'
'\0'
julia> '\u78'
'x'
julia> '\u2200'
'∀'
julia> '\U10ffff'
'\U10ffff'
Julia uses your system’s locale and language settings to determine which characters can be printed as-is and which must be output using the generic, escaped \u or \U input forms. In addition to these Unicode escape forms, all of C’s traditional escaped input forms can also be used:
julia> int('\0')
0
julia> int('\t')
9
julia> int('\n')
10
julia> int('\e')
27
julia> int('\x7f')
127
julia> int('\177')
127
julia> int('\xff')
255
You can do comparisons and a limited amount of arithmetic with Char values:
julia> 'A' < 'a'
true
julia> 'A' <= 'a' <= 'Z'
false
julia> 'A' <= 'X' <= 'Z'
true
julia> 'x' - 'a'
23
julia> 'A' + 1
'B'
Here a variable is initialized with a simple string literal:
julia> str = "Hello, world.\n"
"Hello, world.\n"
If you want to extract a character from a string, you index into it:
julia> str[1]
'H'
julia> str[6]
','
julia> str[end]
'\n'
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 by endof(str)). You can perform arithmetic and other operations with end, just like a normal value:
julia> str[end-1]
'.'
julia> str[end/2]
' '
julia> str[end/3]
'o'
julia> str[end/4]
'l'
Using an index less than 1 or greater than end raises an error:
julia> str[0]
BoundsError()
julia> str[end+1]
BoundsError()
You can also extract a substring using range indexing:
julia> str[4:9]
"lo, wo"
Note the distinction between str[k] and str[k:k]:
julia> str[6]
','
julia> str[6:6]
","
The former is a single character value of type Char, while the latter is a string value that happens to contain only a single character. In Julia these are very different things.
Julia fully supports Unicode characters and strings. As discussed above, in character literals, Unicode code points can be represented using Unicode \u and \U escape sequences, as well as all the standard C escape sequences. These can likewise be used to write string literals:
julia> s = "\u2200 x \u2203 y"
"∀ x ∃ y"
Whether these Unicode characters are displayed as escapes or shown as special characters depends on your terminal’s locale settings and its support for Unicode. Non-ASCII string literals are encoded using the UTF-8 encoding. UTF-8 is a variable-width encoding, meaning that not all characters are encoded in the same number of bytes. In UTF-8, ASCII characters — i.e. those with code points less than 0x80 (128) — are encoded as they are in ASCII, using a single byte, while code points 0x80 and above are encoded using multiple bytes — up to four per character. 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:
julia> s[1]
'∀'
julia> s[2]
invalid UTF-8 character index
julia> s[3]
invalid UTF-8 character index
julia> s[4]
' '
In this case, the character ∀ is a three-byte character, so the indices 2 and 3 are invalid and the next character’s index is 4.
Because of variable-length encodings, the number of character in a string (given by length(s)) is not always the same as the last index. If you iterate through the indices 1 through endof(s) and index into s, the sequence of characters returned, when errors aren’t thrown, is the sequence of characters comprising the string s. Thus, we do have the identity that length(s) <= endof(s) since each character in a string must have its own index. The following is an inefficient and verbose way to iterate through the characters of s:
julia> for i = 1:endof(s)
try
println(s[i])
catch
# ignore the index error
end
end
∀
x
∃
y
The blank lines actually have spaces on them. Fortunately, the above awkward idiom is unnecessary for iterating through the characters in a string, since you can just use the string as an iterable object, no exception handling required:
julia> for c in s
println(c)
end
∀
x
∃
y
UTF-8 is not the only encoding that Julia supports, and adding support for new encodings is quite easy, but discussion of other encodings and how to implement support for them is beyond the scope of this document for the time being. For further discussion of UTF-8 encoding issues, see the section below on byte array literals, which goes into some greater detail.
One of the most common and useful string operations is concatenation:
julia> greet = "Hello"
"Hello"
julia> whom = "world"
"world"
julia> string(greet, ", ", whom, ".\n")
"Hello, world.\n"
Constructing strings like this can become a bit cumbersome, however. To reduce the need for these verbose calls to string, Julia allows interpolation into string literals using $, as in Perl:
julia> "$greet, $whom.\n"
"Hello, world.\n"
This is more readable and convenient and equivalent to the above string concatenation — the system rewrites this apparent single string literal into a concatenation of string literals with variables.
The shortest complete expression after the $ is taken as the expression whose value is to be interpolated into the string. Thus, you can interpolate any expression into a string using parentheses:
julia> "1 + 2 = $(1 + 2)"
"1 + 2 = 3"
Both concatenation and string interpolation call the generic string function to convert objects into String form. Most non-String objects are converted to strings as they are shown in interactive sessions:
julia> v = [1,2,3]
3-element Int64 Array:
1
2
3
julia> "v: $v"
"v: [1, 2, 3]"
The string function is the identity for String and Char values, so these are interpolated into strings as themselves, unquoted and unescaped:
julia> c = 'x'
'x'
julia> "hi, $c"
"hi, x"
To include a literal $ in a string literal, escape it with a backslash:
julia> print("I have \$100 in my account.\n")
I have $100 in my account.
You can lexicographically compare strings using the standard comparison operators:
julia> "abracadabra" < "xylophone"
true
julia> "abracadabra" == "xylophone"
false
julia> "Hello, world." != "Goodbye, world."
true
julia> "1 + 2 = 3" == "1 + 2 = $(1 + 2)"
true
You can search for the index of a particular character using the search function:
julia> search("xylophone", 'x')
1
julia> search("xylophone", 'p')
5
julia> search("xylophone", 'z')
0
You can start the search for a character at a given offset by providing a third argument:
julia> search("xylophone", 'o')
4
julia> search("xylophone", 'o', 5)
7
julia> search("xylophone", 'o', 8)
0
Another handy string function is repeat:
julia> repeat(".:Z:.", 10)
".:Z:..:Z:..:Z:..:Z:..:Z:..:Z:..:Z:..:Z:..:Z:..:Z:."
Some other useful functions include:
There are situations when you want to construct a string or use string semantics, but the behavior of the standard string construct is not quite what is needed. For these kinds of situations, Julia provides non-standard string literals. A non-standard string literal looks like a regular double-quoted string literal, but is immediately prefixed by an identifier, and doesn’t behave quite like a normal string literal. Regular expressions, as described below, are one example of a non-standard string literal. Other examples are given in the metaprogramming section.
Julia has Perl-compatible regular expressions (regexes), as provided by the PCRE library. Regular expressions are related to strings in two ways: the obvious connection is that regular expressions are used to find regular patterns in strings; the other connection is that regular expressions are themselves input as strings, which are parsed into a state machine that can be used to efficiently search for patterns in strings. In Julia, regular expressions are input using non-standard string literals prefixed with various identifiers beginning with r. The most basic regular expression literal without any options turned on just uses r"...":
julia> r"^\s*(?:#|$)"
r"^\s*(?:#|$)"
julia> typeof(ans)
Regex
To check if a regex matches a string, use the ismatch function:
julia> ismatch(r"^\s*(?:#|$)", "not a comment")
false
julia> ismatch(r"^\s*(?:#|$)", "# a comment")
true
As one can see here, ismatch simply returns true or false, indicating whether the given regex matches the string or not. Commonly, however, one wants to know not just whether a string matched, but also how it matched. To capture this information about a match, use the match function instead:
julia> match(r"^\s*(?:#|$)", "not a comment")
julia> match(r"^\s*(?:#|$)", "# a comment")
RegexMatch("#")
If the regular expression does not match the given string, match returns nothing — 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:
m = match(r"^\s*(?:#|$)", line)
if m == nothing
println("not a comment")
else
println("blank or comment")
end
If a regular expression does match, the value returned by match is a RegexMatch object. These objects record how the expression matches, including the substring that the pattern matches and any captured substrings, if there are any. This example only captures the portion of the substring that matches, but perhaps we want to capture any non-blank text after the comment character. We could do the following:
julia> m = match(r"^\s*(?:#\s*(.*?)\s*$|$)", "# a comment ")
RegexMatch("# a comment ", 1="a comment")
You can extract the following info from a RegexMatch object:
For when a capture doesn’t match, instead of a substring, m.captures contains nothing in that position, and m.offsets has a zero offset (recall that indices in Julia are 1-based, so a zero offset into a string is invalid). Here’s is a pair of somewhat contrived examples:
julia> m = match(r"(a|b)(c)?(d)", "acd")
RegexMatch("acd", 1="a", 2="c", 3="d")
julia> m.match
"acd"
julia> m.captures
3-element Union(UTF8String,ASCIIString,Nothing) Array:
"a"
"c"
"d"
julia> m.offset
1
julia> m.offsets
3-element Int64 Array:
1
2
3
julia> m = match(r"(a|b)(c)?(d)", "ad")
RegexMatch("ad", 1="a", 2=nothing, 3="d")
julia> m.match
"ad"
julia> m.captures
3-element Union(UTF8String,ASCIIString,Nothing) Array:
"a"
nothing
"d"
julia> m.offset
1
julia> m.offsets
3-element Int64 Array:
1
0
2
It is convenient to have captures returned as a tuple so that one can use tuple destructuring syntax to bind them to local variables:
julia> first, second, third = m.captures; first
"a"
You can modify the behavior of regular expressions by some combination of the flags i, m, s, and x after the closing double quote mark. These flags have the same meaning as they do in Perl, as explained in this excerpt from the perlre manpage:
i Do case-insensitive pattern matching.
If locale matching rules are in effect, the case map is taken
from the current locale for code points less than 255, and
from Unicode rules for larger code points. However, matches
that would cross the Unicode rules/non-Unicode rules boundary
(ords 255/256) will not succeed.
m Treat string as multiple lines. That is, change "^" and "$"
from matching the start or end of the string to matching the
start or end of any line anywhere within the string.
s Treat string as single line. That is, change "." to match any
character whatsoever, even a newline, which normally it would
not match.
Used together, as r""ms, they let the "." match any character
whatsoever, while still allowing "^" and "$" to match,
respectively, just after and just before newlines within the
string.
x Tells the regular expression parser to ignore most whitespace
that is neither backslashed nor within a character class. You
can use this to break up your regular expression into
(slightly) more readable parts. The '#' character is also
treated as a metacharacter introducing a comment, just as in
ordinary code.
For example, the following regex has all three flags turned on:
julia> r"a+.*b+.*?d$"ism
r"a+.*b+.*?d$"ims
julia> match(r"a+.*b+.*?d$"ism, "Goodbye,\nOh, angry,\nBad world\n")
RegexMatch("angry,\nBad world")
Another useful non-standard string literal is the byte-array string literal: b"...". This form lets you use string notation to express literal byte arrays — i.e. arrays of Uint8 values. The convention is that non-standard literals with uppercase prefixes produce actual string objects, while those with lowercase prefixes produce non-string objects like byte arrays or compiled regular expressions. The rules for byte array literals are the following:
There is some overlap between these rules since the behavior of \x and octal escapes less than 0x80 (128) are covered by both of the first two rules, but here these rules agree. Together, these rules allow one to easily use ASCII characters, arbitrary byte values, and UTF-8 sequences to produce arrays of bytes. Here is an example using all three:
julia> b"DATA\xff\u2200"
[68,65,84,65,255,226,136,128]
The ASCII string “DATA” corresponds to the bytes 68, 65, 84, 65. \xff produces the single byte 255. The Unicode escape \u2200 is encoded in UTF-8 as the three bytes 226, 136, 128. Note that the resulting byte array does not correspond to a valid UTF-8 string — if you try to use this as a regular string literal, you will get a syntax error:
julia> "DATA\xff\u2200"
syntax error: invalid UTF-8 sequence
Also observe the significant distinction between \xff and \uff: the former escape sequence encodes the byte 255, whereas the latter escape sequence represents the code point 255, which is encoded as two bytes in UTF-8:
julia> b"\xff"
1-element Uint8 Array:
0xff
julia> b"\uff"
2-element Uint8 Array:
0xc3
0xbf
In character literals, this distinction is glossed over and \xff is allowed to represent the code point 255, because characters always represent code points. In strings, however, \x escapes always represent bytes, not code points, whereas \u and \U escapes always represent code points, which are encoded in one or more bytes. For code points less than \u80, it happens that the UTF-8 encoding of each code point is just the single byte produced by the corresponding \x escape, so the distinction can safely be ignored. For the escapes \x80 through \xff as compared to \u80 through \uff, however, there is a major difference: the former escapes all encode single bytes, which — unless followed by very specific continuation bytes — do not form valid UTF-8 data, whereas the latter escapes all represent Unicode code points with two-byte encodings.
If this is all extremely confusing, try reading “The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets”. It’s an excellent introduction to Unicode and UTF-8, and may help alleviate some confusion regarding the matter.
In Julia, a function is an object that maps a tuple of argument values to a return value. Julia functions are not pure mathematical functions, in the sense that functions can alter and be affected by the global state of the program. The basic syntax for defining functions in Julia is:
function f(x,y)
x + y
end
There is a second, more terse syntax for defining a function in Julia. The traditional function declaration syntax demonstrated above is equivalent to the following compact “assignment form”:
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 (see Compound Expressions). Short, simple function definitions are common in Julia. The short function syntax is accordingly quite idiomatic, considerably reducing both typing and visual noise.
A function is called using the traditional parenthesis syntax:
julia> f(2,3)
5
Without parentheses, the expression f refers to the function object, and can be passed around like any value:
julia> g = f;
julia> g(2,3)
5
There are two other ways that functions can be applied: using special operator syntax for certain function names (see Operators Are Functions below), or with the apply function:
julia> apply(f,2,3)
5
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. This is the same behavior found in Scheme, most Lisps, Python, Ruby and Perl, among other dynamic languages.
The value returned by a function is the value of the last expression evaluated, which, by default, is the last expression in the body of the function definition. In the example function, f, from the previous section this is the value of the expression x + y. As in C and most other imperative or functional languages, the return keyword causes a function to return immediately, providing an expression whose value is returned:
function g(x,y)
return x * y
x + y
end
Since functions definitions can be entered into interactive sessions, it is easy to compare these definitions:
f(x,y) = x + y
function g(x,y)
return x * y
x + y
end
julia> f(2,3)
5
julia> g(2,3)
6
Of course, in a purely linear function body like g, the usage of return is pointless since the expression x + y is never evaluated and we could simply make x * y the last expression in the function and omit the return. In conjunction with other control flow, however, return is of real use. Here, for example, is a function that computes the hypotenuse length of a right triangle with sides of length x and y, avoiding overflow:
function hypot(x,y)
x = abs(x)
y = abs(y)
if x > y
r = y/x
return x*sqrt(1+r*r)
end
if y == 0
return zero(x)
end
r = x/y
return y*sqrt(1+r*r)
end
There are three possible points of return from this function, returning the values of three different expressions, depending on the values of x and y. The return on the last line could be omitted since it is the last expression.
In Julia, most operators are just functions with support for special syntax. The exceptions are operators with special evaluation semantics like && and ||. These operators cannot be functions since short-circuit evaluation requires that their operands are not evaluated before evaluation of the operator. Accordingly, you can also apply them using parenthesized argument lists, just as you would any other function:
julia> 1 + 2 + 3
6
julia> +(1,2,3)
6
The infix form is exactly equivalent to the function application form — in fact the former is parsed to produce the function call internally. This also means that you can assign and pass around operators such as + and * just like you would with other function values:
julia> f = +;
julia> f(1,2,3)
6
Under the name f, the function does not support infix notation, however.
A few special expressions correspond to calls to functions with non-obvious names. These are:
Expression | Calls |
---|---|
[A B C ...] | hcat |
[A, B, C, ...] | vcat |
[A B; C D; ...] | hvcat |
A' | ctranspose |
A.' | transpose |
1:n | colon |
A[i] | getindex |
A[i]=x | setindex! |
These functions are included in the Base.Operators module even though they do not have operator-like names.
Functions in Julia are first-class objects: they can be assigned to variables, called using the standard function call syntax from the variable they have been assigned to. They can be used as arguments, and they can be returned as values. They can also be created anonymously, without being given a name:
julia> x -> x^2 + 2x - 1
#<function>
This creates an unnamed function taking one argument x and returning the value of the polynomial x^2 + 2x - 1 at that value. The primary use for anonymous functions is passing them to functions which take other functions as arguments. A classic example is the map function, which applies a function to each value of an array and returns a new array containing the resulting values:
julia> map(round, [1.2,3.5,1.7])
3-element Float64 Array:
1.0
4.0
2.0
This is fine if a named function effecting the transform one wants already exists to pass as the first argument to map. Often, however, a ready-to-use, named function does not exist. In these situations, the anonymous function construct allows easy creation of a single-use function object without needing a name:
julia> map(x -> x^2 + 2x - 1, [1,3,-1])
3-element Int64 Array:
2
14
-2
An anonymous function accepting multiple arguments can be written using the syntax (x,y,z)->2x+y-z. A zero-argument anonymous function is written as ()->3. The idea of a function with no arguments may seem strange, but is useful for “delaying” a computation. In this usage, a block of code is wrapped in a zero-argument function, which is later invoked by calling it as f().
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. For example, the following function returns a pair of values:
function foo(a,b)
a+b, a*b
end
If you call it in an interactive session without assigning the return value anywhere, you will see the tuple returned:
julia> foo(2,3)
(5,6)
A typical usage of such a pair of return values, however, extracts each value into a variable. Julia supports simple tuple “destructuring” that facilitates this:
julia> x, y = foo(2,3);
julia> x
5
julia> y
6
You can also return multiple values via an explicit usage of the return keyword:
function foo(a,b)
return a+b, a*b
end
This has the exact same effect as the previous definition of foo.
It is often convenient to be able to write functions taking an arbitrary number of arguments. Such functions are traditionally known as “varargs” functions, which is short for “variable number of arguments”. You can define a varargs function by following the last argument with an ellipsis:
bar(a,b,x...) = (a,b,x)
The variables a and b are bound to the first two argument values as usual, and the variable x is bound to an iterable collection of the zero or more values passed to bar after its first two arguments:
julia> bar(1,2)
(1,2,())
julia> bar(1,2,3)
(1,2,(3,))
julia> bar(1,2,3,4)
(1,2,(3,4))
julia> bar(1,2,3,4,5,6)
(1,2,(3,4,5,6))
In all these cases, x is bound to a tuple of the trailing values passed to bar.
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:
julia> x = (3,4)
(3,4)
julia> bar(1,2,x...)
(1,2,(3,4))
In this case a tuple of values is spliced into a varargs call precisely where the variable number of arguments go. This need not be the case, however:
julia> x = (2,3,4)
(2,3,4)
julia> bar(1,x...)
(1,2,(3,4))
julia> x = (1,2,3,4)
(1,2,3,4)
julia> bar(x...)
(1,2,(3,4))
Furthermore, the iterable object spliced into a function call need not be a tuple:
julia> x = [3,4]
2-element Int64 Array:
3
4
julia> bar(1,2,x...)
(1,2,(3,4))
julia> x = [1,2,3,4]
4-element Int64 Array:
1
2
3
4
julia> bar(x...)
(1,2,(3,4))
Also, the function that arguments are spliced into need not be a varargs function (although it often is):
baz(a,b) = a + b
julia> args = [1,2]
2-element Int64 Array:
1
2
julia> baz(args...)
3
julia> args = [1,2,3]
3-element Int64 Array:
1
2
3
julia> baz(args...)
no method baz(Int64,Int64,Int64)
As you can see, if the wrong number of elements are in the spliced container, then the function call will fail, just as it would if too many arguments were given explicitly.
In many cases, function arguments have sensible default values and therefore might not need to be passed explicitly in every call. For example, the library function parseint(num,base) interprets a string as a number in some base. The base argument defaults to 10. This behavior can be expressed concisely as:
function parseint(num, base=10)
###
end
With this definition, the function can be called with either one or two arguments, and 10 is automatically passed when a second argument is not specified:
julia> parseint("12",10)
12
julia> parseint("12",3)
5
julia> parseint("12")
12
Optional arguments are actually just a convenient syntax for writing multiple method definitions with different numbers of arguments (see Methods).
Some functions need a large number of arguments, or have a large number of behaviors. Remembering how to call such functions can be difficult. Keyword arguments can make these complex interfaces easier to use and extend by allowing arguments to be identified by name instead of only by position.
For example, consider a function plot that plots a line. This function might have many options, for controlling line style, width, color, and so on. If it accepts keyword arguments, a possible call might look like plot(x, y, width=2), where we have chosen to specify only line width. Notice that this serves two purposes. The call is easier to read, since we can label an argument with its meaning. It also becomes possible to pass any subset of a large number of arguments, in any order.
Functions with keyword arguments are defined using a semicolon in the signature:
function plot(x, y; style="solid", width=1, color="black")
###
end
Extra keyword arguments can be collected using ..., as in varargs functions:
function f(x; args...)
###
end
Inside f, args will be a collection of (key,value) tuples, where each key is a symbol. Such collections can be passed as keyword arguments using a semicolon in a call, f(x; k...). Dictionaries can be used for this purpose.
Keyword argument default values are evaluated only when necessary (when a corresponding keyword argument is not passed), and in left-to-right order. Therefore default expressions may refer to prior keyword arguments.
Optional and keyword arguments differ slightly in how their default values are evaluated. When optional argument default expressions are evaluated, only previous arguments are in scope. For example, given this definition:
function f(x, a=b, b=1)
###
end
the b in a=b refers to the b in an outer scope, not the subsequent argument b. However, if a and b were keyword arguments instead, then both would be created in the same scope and a=b would result in an undefined variable error (since the default expressions are evaluated left-to-right, and b has not been assigned yet).
Passing functions as arguments to other functions is a powerful technique, but the syntax for it is not always convenient. Such calls are especially awkward to write when the function argument requires multiple lines. As an example, consider calling map on a function with several cases:
map(x->begin
if x < 0 && iseven(x)
return 0
elseif x == 0
return 1
else
return x
end
end,
[A, B, C])
Julia provides a reserved word do for rewriting this code more clearly:
map([A, B, C]) do x
if x < 0 && iseven(x)
return 0
elseif x == 0
return 1
else
return x
end
end
The do x syntax creates an anonymous function with argument x and passes it as the first argument to map. This syntax makes it easier to use functions to effectively extend the language, since calls look like normal code blocks. There are many possible uses quite different from map, such as managing system state. For example, the standard library provides a function cd for running code in a given directory, and switching back to the previous directory when the code finishes or aborts. There is also a definition of open that runs code ensuring that the opened file is eventually closed. We can combine these functions to safely write a file in a certain directory:
cd("data") do
open("outfile", "w") do f
write(f, data)
end
end
The function argument to cd takes no arguments; it is just a block of code. The function argument to open receives a handle to the opened file.
We should mention here that this is far from a complete picture of defining functions. Julia has a sophisticated type system and allows multiple dispatch on argument types. None of the examples given here provide any type annotations on their arguments, meaning that they are applicable to all types of arguments. The type system is described in Types and defining a function in terms of methods chosen by multiple dispatch on run-time argument types is described in Methods.
Julia provides a variety of control flow constructs:
The first five control flow mechanisms are standard to high-level programming languages. Tasks are not so standard: they provide non-local control flow, making it possible to switch between temporarily-suspended computations. This is a powerful construct: both exception handling and cooperative multitasking are implemented in Julia using tasks. Everyday programming requires no direct usage of tasks, but certain problems can be solved much more easily by using tasks.
Sometimes it is convenient to have a single expression which evaluates several subexpressions in order, returning the value of the last subexpression as its value. There are two Julia constructs that accomplish this: begin blocks and (;) chains. The value of both compound expression constructs is that of the last subexpression. Here’s an example of a begin block:
julia> z = begin
x = 1
y = 2
x + y
end
3
Since these are fairly small, simple expressions, they could easily be placed onto a single line, which is where the (;) chain syntax comes in handy:
julia> z = (x = 1; y = 2; x + y)
3
This syntax is particularly useful with the terse single-line function definition form introduced in Functions. Although it is typical, there is no requirement that begin blocks be multiline or that (;) chains be single-line:
julia> begin x = 1; y = 2; x + y end
3
julia> (x = 1;
y = 2;
x + y)
3
Conditional evaluation allows portions of code to be evaluated or not evaluated depending on the value of a boolean expression. Here is the anatomy of the if-elseif-else conditional syntax:
if x < y
println("x is less than y")
elseif x > y
println("x is greater than y")
else
println("x is equal to y")
end
If the condition expression x < y is true, then the corresponding block is evaluated; otherwise the condition expression x > y is evaluated, and if it is true, the corresponding block is evaluated; if neither expression is true, the else block is evaluated. Here it is in action:
julia> function test(x, y)
if x < y
println("x is less than y")
elseif x > y
println("x is greater than y")
else
println("x is equal to y")
end
end
test (generic function with 1 method)
julia> test(1, 2)
x is less than y
julia> test(2, 1)
x is greater than y
julia> test(1, 1)
x is equal to y
The elseif and else blocks are optional, and as many elseif blocks as desired can be used. The condition expressions in the if-elseif-else construct are evaluated until the first one evaluates to true, after which the associated block is evaluated, and no further condition expressions or blocks are evaluated.
Unlike C, MATLAB, Perl, Python, and Ruby — but like Java, and a few other stricter, typed languages — it is an error if the value of a conditional expression is anything but true or false:
julia> if 1
println("true")
end
type error: lambda: in if, expected Bool, got Int64
This error indicates that the conditional was of the wrong type: Int64 rather than the required Bool.
The so-called “ternary operator”, ?:, is closely related to the if-elseif-else syntax, but is used where a conditional choice between single expression values is required, as opposed to conditional execution of longer blocks of code. It gets its name from being the only operator in most languages taking three operands:
a ? b : c
The expression a, before the ?, is a condition expression, and the ternary operation evaluates the expression b, before the :, if the condition a is true or the expression c, after the :, if it is false.
The easiest way to understand this behavior is to see an example. In the previous example, the println call is shared by all three branches: the only real choice is which literal string to print. This could be written more concisely using the ternary operator. For the sake of clarity, let’s try a two-way version first:
julia> x = 1; y = 2;
julia> println(x < y ? "less than" : "not less than")
less than
julia> x = 1; y = 0;
julia> println(x < y ? "less than" : "not less than")
not less than
If the expression x < y is true, the entire ternary operator expression evaluates to the string "less than" and otherwise it evaluates to the string "not less than". The original three-way example requires chaining multiple uses of the ternary operator together:
julia> test(x, y) = println(x < y ? "x is less than y" :
x > y ? "x is greater than y" : "x is equal to y")
test (generic function with 1 method)
julia> test(1, 2)
x is less than y
julia> test(2, 1)
x is greater than y
julia> test(1, 1)
x is equal to y
To facilitate chaining, the operator associates from right to left.
It is significant that like if-elseif-else, the expressions before and after the : are only evaluated if the condition expression evaluates to true or false, respectively:
julia> v(x) = (println(x); x)
v (generic function with 1 method)
julia> 1 < 2 ? v("yes") : v("no")
yes
"yes"
julia> 1 > 2 ? v("yes") : v("no")
no
"no"
Short-circuit evaluation is quite similar to conditional evaluation. The behavior is found in most imperative programming languages having the && and || boolean operators: in a series of boolean expressions connected by these operators, only the minimum number of expressions are evaluated as are necessary to determine the final boolean value of the entire chain. Explicitly, this means that:
The reasoning is that a && b must be false if a is false, regardless of the value of b, and likewise, the value of a || b must be true if a is true, regardless of the value of b. Both && and || associate to the right, but && has higher precedence than || does. It’s easy to experiment with this behavior:
julia> t(x) = (println(x); true)
t (generic function with 1 method)
julia> f(x) = (println(x); false)
f (generic function with 1 method)
julia> t(1) && t(2)
1
2
true
julia> t(1) && f(2)
1
2
false
julia> f(1) && t(2)
1
false
julia> f(1) && f(2)
1
false
julia> t(1) || t(2)
1
true
julia> t(1) || f(2)
1
true
julia> f(1) || t(2)
1
2
true
julia> f(1) || f(2)
1
2
false
You can easily experiment in the same way with the associativity and precedence of various combinations of && and || operators.
Boolean operations without short-circuit evaluation can be done with the bitwise boolean operators introduced in Mathematical Operations and Elementary Functions: & and |. These are normal functions, which happen to support infix operator syntax, but always evaluate their arguments:
julia> f(1) & t(2)
1
2
false
julia> t(1) | t(2)
1
2
true
Just like condition expressions used in if, elseif or the ternary operator, the operands of && or || must be boolean values (true or false). Using a non-boolean value is an error:
julia> 1 && 2
type error: lambda: in if, expected Bool, got Int64
There are two constructs for repeated evaluation of expressions: the while loop and the for loop. Here is an example of a while loop:
julia> i = 1;
julia> while i <= 5
println(i)
i += 1
end
1
2
3
4
5
The while loop evaluates the condition expression (i < n in this case), and as long it remains true, keeps also evaluating the body of the while loop. If the condition expression is false when the while loop is first reached, the body is never evaluated.
The for loop makes common repeated evaluation idioms easier to write. Since counting up and down like the above while loop does is so common, it can be expressed more concisely with a for loop:
julia> for i = 1:5
println(i)
end
1
2
3
4
5
Here the 1:5 is a Range object, representing the sequence of numbers 1, 2, 3, 4, 5. The for loop iterates through these values, assigning each one in turn to the variable i. One rather important distinction between the previous while loop form and the for loop form is the scope during which the variable is visible. If the variable i has not been introduced in an other scope, in the for loop form, it is visible only inside of the for loop, and not afterwards. You’ll either need a new interactive session instance or a different variable name to test this:
julia> for j = 1:5
println(j)
end
1
2
3
4
5
julia> j
j not defined
See Scope of Variables for a detailed explanation of variable scope and how it works in Julia.
In general, the for loop construct can iterate over any container. In these cases, the alternative (but fully equivalent) keyword in is typically used instead of =, since it makes the code read more clearly:
julia> for i in [1,4,0]
println(i)
end
1
4
0
julia> for s in ["foo","bar","baz"]
println(s)
end
foo
bar
baz
Various types of iterable containers will be introduced and discussed in later sections of the manual (see, e.g., Multi-dimensional Arrays).
It is sometimes convenient to terminate the repetition of a while before the test condition is falsified or stop iterating in a for loop before the end of the iterable object is reached. This can be accomplished with the break keyword:
julia> i = 1;
julia> while true
println(i)
if i >= 5
break
end
i += 1
end
1
2
3
4
5
julia> for i = 1:1000
println(i)
if i >= 5
break
end
end
1
2
3
4
5
The above while loop would never terminate on its own, and the for loop would iterate up to 1000. These loops are both exited early by using the break keyword.
In other circumstances, it is handy to be able to stop an iteration and move on to the next one immediately. The continue keyword accomplishes this:
julia> for i = 1:10
if i % 3 != 0
continue
end
println(i)
end
3
6
9
This is a somewhat contrived example since we could produce the same behavior more clearly by negating the condition and placing the println call inside the if block. In realistic usage there is more code to be evaluated after the continue, and often there are multiple points from which one calls continue.
Multiple nested for loops can be combined into a single outer loop, forming the cartesian product of its iterables:
julia> for i = 1:2, j = 3:4
println((i, j))
end
(1,3)
(1,4)
(2,3)
(2,4)
When an unexpected condition occurs, a function may be unable to return a reasonable value to its caller. In such cases, it may be best for the exceptional condition to either terminate the program, printing a diagnostic error message, or if the programmer has provided code to handle such exceptional circumstances, allow that code to take the appropriate action.
Exceptions are thrown when an unexpected condition has occurred. The built-in Exceptions listed below all interrupt the normal flow of control.
Exception |
---|
ArgumentError |
BoundsError |
DivideError |
DomainError |
EOFError |
ErrorException |
InexactError |
InterruptException |
KeyError |
LoadError |
MemoryError |
MethodError |
OverflowError |
ParseError |
SystemError |
TypeError |
UndefRefError |
For example, the sqrt function throws a DomainError() if applied to a negative real value:
julia> sqrt(-1)
ERROR: DomainError()
in sqrt at math.jl:117
Exceptions can be created explicitly with throw. For example, a function defined only for nonnegative numbers could be written to throw a DomainError if the argument is negative.
julia> f(x) = x>=0 ? exp(-x) : throw(DomainError())
f (generic function with 1 method)
julia> f(1)
0.36787944117144233
julia> f(-1)
ERROR: DomainError()
in f at none:1
Note that DomainError without parentheses is not an exception, but a type of exception. It needs to be called to obtain an Exception object
julia> typeof(DomainError()) <: Exception
true
julia> typeof(DomainError) <: Exception
false
The error function is used to produce an ErrorException that interrupts the normal flow of control.
Suppose we want to stop execution immediately if the square root of a negative number is taken. To do this, we can define a fussy version of the sqrt function that raises an error if its argument is negative:
julia> fussy_sqrt(x) = x >= 0 ? sqrt(x) : error("negative x not allowed")
fussy_sqrt (generic function with 1 method)
julia> fussy_sqrt(2)
1.4142135623730951
julia> fussy_sqrt(-1)
negative x not allowed
If fussy_sqrt is called with a negative value from another function, instead of trying to continue execution of the calling function, it returns immediately, displaying the error message in the interactive session:
julia> function verbose_fussy_sqrt(x)
println("before fussy_sqrt")
r = fussy_sqrt(x)
println("after fussy_sqrt")
return r
end
verbose_fussy_sqrt (generic function with 1 method)
julia> verbose_fussy_sqrt(2)
before fussy_sqrt
after fussy_sqrt
1.4142135623730951
julia> verbose_fussy_sqrt(-1)
before fussy_sqrt
negative x not allowed
Julia also provides other functions that write messages to the standard error I/O, but do not throw any Exceptions and hence do not interrupt execution.:
julia> info("Hi"); 1+1
MESSAGE: Hi
2
julia> warn("Hi"); 1+1
WARNING: Hi
2
julia> error("Hi"); 1+1
ERROR: Hi
in error at error.jl:21
The try/catch statement allows for Exceptions to be tested for. For example, a customized square root function can be written to automatically call either the real or complex square root method on demand using Exceptions
julia> f(x) = try
sqrt(x)
catch
sqrt(complex(x, 0))
end
f (generic function with 1 method)
julia> f(1)
1.0
julia> f(-1)
0.0 + 1.0im
It is important to note that in real code computing this function, one would compare x to zero instead of catching an exception. The exception is much slower than simply comparing and branching.
try/catch statements also allow the Exception to be saved in a variable. In this contrived example, the following example calculates the square root of the second element of x if x is indexable, otherwise assumes x is a real number and returns its square root:
julia> sqrt_second(x) = try
sqrt(x[2])
catch y
if isa(y, DomainError)
sqrt(complex(x[2], 0))
elseif isa(y, BoundsError)
sqrt(x)
end
end
sqrt_second (generic function with 1 method)
julia> sqrt_second([1 4])
2.0
julia> sqrt_second([1 -4])
0.0 + 2.0im
julia> sqrt_second(9)
3.0
julia> sqrt_second(-9)
ERROR: DomainError()
in sqrt at math.jl:117
in sqrt_second at none:7
The power of the try/catch construct lies in the ability to unwind a deeply nested computation immediately to a much higher level in the stack of calling functions. There are situations where no error has occurred, but the ability to unwind the stack and pass a value to a higher level is desirable. Julia provides the rethrow, backtrace and catch_backtrace functions for more advanced error handling.
In code that performs state changes or uses resources like files, there is typically clean-up work (such as closing files) that needs to be done when the code is finished. Exceptions potentially complicate this task, since they can cause a block of code to exit before reaching its normal end. The finally keyword provides a way to run some code when a given block of code exits, regardless of how it exits.
For example, here is how we can guarantee that an opened file is closed:
f = open("file")
try
# operate on file f
finally
close(f)
end
When control leaves the try block (for example due to a return, or just finishing normally), close(f) will be executed. If the try block exits due to an exception, the exception will continue propagating. A catch block may be combined with try and finally as well. In this case the finally block will run after catch has handled the error.
Tasks are a control flow feature that allows computations to be suspended and resumed in a flexible manner. This feature is sometimes called by other names, such as symmetric coroutines, lightweight threads, cooperative multitasking, or one-shot continuations.
When a piece of computing work (in practice, executing a particular function) is designated as a Task, it becomes possible to interrupt it by switching to another Task. The original Task can later be resumed, at which point it will pick up right where it left off. At first, this may seem similar to a function call. However there are two key differences. First, switching tasks does not use any space, so any number of task switches can occur without consuming the call stack. Second, switching among tasks can occur in any order, unlike function calls, where the called function must finish executing before control returns to the calling function.
This kind of control flow can make it much easier to solve certain problems. In some problems, the various pieces of required work are not naturally related by function calls; there is no obvious “caller” or “callee” among the jobs that need to be done. An example is the producer-consumer problem, where one complex procedure is generating values and another complex procedure is consuming them. The consumer cannot simply call a producer function to get a value, because the producer may have more values to generate and so might not yet be ready to return. With tasks, the producer and consumer can both run as long as they need to, passing values back and forth as necessary.
Julia provides the functions produce and consume for solving this problem. A producer is a function that calls produce on each value it needs to produce:
function producer()
produce("start")
for n=1:4
produce(2n)
end
produce("stop")
end
To consume values, first the producer is wrapped in a Task, then consume is called repeatedly on that object:
julia> p = Task(producer)
Task
julia> consume(p)
"start"
julia> consume(p)
2
julia> consume(p)
4
julia> consume(p)
6
julia> consume(p)
8
julia> consume(p)
"stop"
One way to think of this behavior is that producer was able to return multiple times. Between calls to produce, the producer’s execution is suspended and the consumer has control.
A Task can be used as an iterable object in a for loop, in which case the loop variable takes on all the produced values:
julia> for x in Task(producer)
println(x)
end
start
2
4
6
8
stop
Note that the Task() constructor expects a 0-argument function. A common pattern is for the producer to be parameterized, in which case a partial function application is needed to create a 0-argument anonymous function. This can be done either directly or by use of a convenience macro:
function mytask(myarg)
...
end
taskHdl = Task(() -> mytask(7))
# or, equivalently
taskHdl = @task mytask(7)
produce and consume are intended for multitasking, and do not launch threads that can run on separate CPUs. True kernel threads are discussed under the topic of Parallel Computing.
The scope of a variable is the region of code within which a variable is visible. Variable scoping helps avoid variable naming conflicts. The concept is intuitive: two functions can both have arguments called x without the two x‘s referring to the same thing. Similarly there are many other cases where different blocks of code can use the same name without referring to the same thing. The rules for when the same variable name does or doesn’t refer to the same thing are called scope rules; this section spells them out in detail.
Certain constructs in the language introduce scope blocks, which are regions of code that are eligible to be the scope of some set of variables. The scope of a variable cannot be an arbitrary set of source lines; instead, it will always line up with one of these blocks. The constructs introducing such blocks are:
Notably missing from this list are begin blocks, which do not introduce new scope blocks.
Certain constructs introduce new variables into the current innermost scope. 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. These constructs which introduce new variables into the current scope are as follows:
In the following example, there is only one x assigned both inside and outside the for loop:
function foo(n)
x = 0
for i = 1:n
x = x + 1
end
x
end
julia> foo(10)
10
In the next example, the loop has a separate x and the function always returns zero:
function foo(n)
x = 0
for i = 1:n
local x
x = i
end
x
end
julia> foo(10)
0
In this example, an x exists only inside the loop, and the function encounters an undefined variable error on its last line (unless there is a global variable x):
function foo(n)
for i = 1:n
x = i
end
x
end
julia> foo(10)
in foo: x not defined
A variable that is not assigned to or otherwise introduced locally defaults to global, so this function would return the value of the global x if there were such a variable, or produce an error if no such global existed. As a consequence, the only way to assign to a global variable inside a non-top-level scope is to explicitly declare the variable as global within some scope, since otherwise the assignment would introduce a new local rather than assigning to the global. This rule works out well in practice, since the vast majority of variables assigned inside functions are intended to be local variables, and using global variables should be the exception rather than the rule, and assigning new values to them even more so.
One last example shows that an outer assignment introducing x need not come before an inner usage:
function foo(n)
f = y -> n + x + y
x = 1
f(2)
end
julia> foo(10)
13
This behavior may seem slightly odd for a normal variable, but allows for named functions — which are just normal variables holding function objects — to be used before they are defined. This allows functions to be defined in whatever order is intuitive and convenient, rather than forcing bottom up ordering or requiring forward declarations, both of which one typically sees in C programs. As an example, here is an inefficient, mutually recursive way to test if positive integers are even or odd:
even(n) = n == 0 ? true : odd(n-1)
odd(n) = n == 0 ? false : even(n-1)
julia> even(3)
false
julia> odd(3)
true
Julia provides built-in, efficient functions to test this called iseven and isodd so the above definitions should only be taken as examples.
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.
At the interactive prompt, variable scope works the same way as anywhere else. The prompt behaves as if there is scope block wrapped around everything you type, except that this scope block is identified with the global scope. This is especially evident in the case of assignments:
julia> for i = 1:1; y = 10; end
julia> y
y not defined
julia> y = 0
0
julia> for i = 1:1; y = 10; end
julia> y
10
In the former case, y only exists inside of the for loop. In the latter case, an outer y has been introduced and so is inherited within the loop. Due to the special identification of the prompt’s scope block with the global scope, it is not necessary to declare global y inside the loop. However, in code not entered into the interactive prompt this declaration would be necessary in order to modify a global variable.
The let statement provides a different way to introduce variables. Unlike assignments to local variables, let statements allocate new variable bindings each time they run. An assignment modifies an existing value location, and let creates new locations. This difference is usually not important, and is only detectable in the case of variables that outlive their scope via closures. The let syntax accepts a comma-separated series of assignments and variable names:
let var1 = value1, var2, var3 = value3
code
end
The assignments are evaluated in order, with each right-hand side evaluated in the scope before the new variable on the left-hand side has been introduced. Therefore it makes sense to write something like let x = x since the two x variables are distinct and have separate storage. Here is an example where the behavior of let is needed:
Fs = cell(2)
i = 1
while i <= 2
Fs[i] = ()->i
i += 1
end
julia> Fs[1]()
3
julia> Fs[2]()
3
Here we create and store two closures that return variable i. However, it is always the same variable i, so the two closures behave identically. We can use let to create a new binding for i:
Fs = cell(2)
i = 1
while i <= 2
let i = i
Fs[i] = ()->i
end
i += 1
end
julia> Fs[1]()
1
julia> Fs[2]()
2
Since the begin construct does not introduce a new scope, it can be useful to use a zero-argument let to just introduce a new scope block without creating any new bindings:
julia> begin
local x = 1
begin
local x = 2
end
x
end
syntax error: local x declared twice
julia> begin
local x = 1
let
local x = 2
end
x
end
1
The first example is illegal because you cannot declare the same variable as local in the same scope twice. The second example is legal since the let introduces a new scope block, so the inner local x is a different variable than the outer local x.
For loops and comprehensions have a special additional behavior: any new variables introduced in their body scopes are freshly allocated for each loop iteration. Therefore these constructs are similar to while loops with let blocks inside:
Fs = cell(2)
for i = 1:2
Fs[i] = ()->i
end
julia> Fs[1]()
1
julia> Fs[2]()
2
for loops will reuse existing variables for iteration:
i = 0
for i = 1:3
end
i # here equal to 3
However, comprehensions do not do this, and always freshly allocate their iteration variables:
x = 0
[ x for x=1:3 ]
x # here still equal to 0
A common use of variables is giving names to specific, unchanging values. Such variables are only assigned once. This intent can be conveyed to the compiler using the const keyword:
const e = 2.71828182845904523536
const pi = 3.14159265358979323846
The const declaration is allowed on both global and local variables, but is especially useful for globals. It is difficult for the compiler to optimize code involving global variables, since their values (or even their types) might change at almost any time. If a global variable will not change, adding a const declaration solves this performance problem.
Local constants are quite different. The compiler is able to determine automatically when a local variable is constant, so local constant declarations are not necessary for performance purposes.
Special top-level assignments, such as those performed by the function and type keywords, are constant by default.
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.
Type systems have traditionally fallen into two quite different camps: static type systems, where every program expression must have a type computable before the execution of the program, and dynamic type systems, where nothing is known about types until run time, when the actual values manipulated by the program are available. Object orientation allows some flexibility in statically typed languages by letting code be written without the precise types of values being known at compile time. The ability to write code that can operate on different types is called polymorphism. All code in classic dynamically typed languages is polymorphic: only by explicitly checking types, or when objects fail to support operations at run-time, are the types of any values ever restricted.
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. This can be of great assistance in generating efficient code, but even more significantly, it allows method dispatch on the types of function arguments to be deeply integrated with the language. Method dispatch is explored in detail in Methods, but is rooted in the type system presented here.
The default behavior in Julia when types are omitted is to allow values to be of any type. Thus, one can write many useful Julia programs without ever explicitly using types. When additional expressiveness is needed, however, it is easy to gradually introduce explicit type annotations into previously “untyped” code. Doing so will typically increase both the performance and robustness of these systems, and perhaps somewhat counterintuitively, often significantly simplify them.
Describing Julia in the lingo of type systems, it is: dynamic, nominative, parametric and dependent. Generic types can be parameterized, and the hierarchical relationships between types are explicitly declared, rather than implied by compatible structure. 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. While this might at first seem unduly restrictive, it has many beneficial consequences with surprisingly few drawbacks. It turns out that being able to inherit behavior is much more important than being able to inherit structure, and inheriting both causes significant difficulties in traditional object-oriented languages. Other high-level aspects of Julia’s type system that should be mentioned up front are:
Julia’s type system is designed to be powerful and expressive, yet clear, intuitive and unobtrusive. Many Julia programmers may never feel the need to write code that explicitly uses types. Some kinds of programming, however, become clearer, simpler, faster and more robust with declared types.
The :: operator can be used to attach type annotations to expressions and variables in programs. There are two primary reasons to do this:
The :: operator is read as “is an instance of” and can be used anywhere to assert that the value of the expression on the left is an instance of the type on the right. When the type on the right is concrete, the value on the left must have that type as its implementation — recall that all concrete types are final, so no implementation is a subtype of any other. When the type is abstract, it suffices for the value to be implemented by a concrete type that is a subtype of the abstract type. If the type assertion is not true, an exception is thrown, otherwise, the left-hand value is returned:
julia> (1+2)::FloatingPoint
ERROR: type: typeassert: expected FloatingPoint, got Int64
julia> (1+2)::Int
3
This allows a type assertion to be attached to any expression in-place.
When attached to a variable, 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 the convert function:
julia> function foo()
x::Int8 = 1000
x
end
julia> foo()
-24
julia> typeof(ans)
Int8
This feature is useful for avoiding performance “gotchas” that could occur if one of the assignments to a variable changed its type unexpectedly.
The “declaration” behavior only occurs in specific contexts:
x::Int8 # a variable by itself
local x::Int8 # in a local declaration
x::Int8 = 10 # as the left-hand side of an assignment
In value contexts, such as f(x::Int8), the :: is a type assertion again and not a declaration. Note that these declarations cannot be used in global scope currently, 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. We begin with abstract types even though they have no instantiation because they are the backbone of the type system: they form the conceptual hierarchy which makes Julia’s type system more than just a collection of object implementations.
Recall that in Integers and Floating-Point Numbers, we introduced a variety of concrete types of numeric values: Int8, Uint8, Int16, Uint16, Int32, Uint32, Int64, Uint64, Int128, Uint128, Float16, Float32, and Float64. Although they have different representation sizes, Int8, Int16, Int32, Int64 and Int128 all have in common that they are signed integer types. Likewise Uint8, Uint16, Uint32, Uint64 and Uint128 are all unsigned integer types, while Float16, Float32 and Float64 are distinct in being floating-point types rather than integers. It is common for a piece of code to make sense, for example, only if its arguments are some kind of integer, but not really depend on what particular kind of integer. For example, the greatest common denominator algorithm works for all kinds of integers, but will not work for floating-point numbers. Abstract types allow the construction of a hierarchy of types, providing a context into which concrete types can fit. This allows you, for example, to easily program to any type that is an integer, without restricting an algorithm to a specific type of integer.
Abstract types are declared using the abstract keyword. The general syntaxes for declaring an abstract type are:
abstract «name»
abstract «name» <: «supertype»
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.
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. In type theory, Any is commonly called “top” because it is at the apex of the type graph. Julia also has a predefined abstract “bottom” type, at the nadir of the type graph, which is called None. It is the exact opposite of Any: no object is an instance of None and all types are supertypes of None.
Let’s consider some of the abstract types that make up Julia’s numerical hierarchy:
abstract Number
abstract Real <: Number
abstract FloatingPoint <: Real
abstract Integer <: Real
abstract Signed <: Integer
abstract Unsigned <: Integer
The Number type is a direct child type of Any, and Real is its child. In turn, Real has two children (it has more, but only two are shown here; we’ll get to the others later): Integer and FloatingPoint, separating the world into representations of integers and representations of real numbers. Representations of real numbers include, of course, floating-point types, but also include other types, such as rationals. Hence, FloatingPoint is a proper subtype of Real, including only floating-point representations of real numbers. Integers are further subdivided into Signed and Unsigned varieties.
The <: operator in general means “is a subtype of”, and, used in declarations like this, declares the right-hand type to be an immediate supertype of the newly declared type. It can also be used in expressions as a subtype operator which returns true when its left operand is a subtype of its right operand:
julia> Integer <: Number
true
julia> Integer <: FloatingPoint
false
Since abstract types have no instantiations and serve as no more than nodes in the type graph, there is not much more to say about them until we introduce parametric abstract types later on in Parametric Types.
A bits type is a concrete type whose data consists of plain old bits. Classic examples of bits types are integers and floating-point values. Unlike most languages, Julia lets you declare your own bits types, rather than providing only a fixed set of built-in bits types. In fact, the standard bits types are all defined in the language itself:
bitstype 16 Float16 <: FloatingPoint
bitstype 32 Float32 <: FloatingPoint
bitstype 64 Float64 <: FloatingPoint
bitstype 8 Bool <: Integer
bitstype 32 Char <: Integer
bitstype 8 Int8 <: Signed
bitstype 8 Uint8 <: Unsigned
bitstype 16 Int16 <: Signed
bitstype 16 Uint16 <: Unsigned
bitstype 32 Int32 <: Signed
bitstype 32 Uint32 <: Unsigned
bitstype 64 Int64 <: Signed
bitstype 64 Uint64 <: Unsigned
bitstype 128 Int128 <: Signed
bitstype 128 Uint128 <: Unsigned
The general syntaxes for declaration of a bitstype are:
bitstype «bits» «name»
bitstype «bits» «name» <: «supertype»
The number of bits indicates how much storage the type requires and the name gives the new type a name. A bits type can optionally be declared to be a subtype of some supertype. If a supertype is omitted, then the type defaults to having Any as its immediate supertype. The declaration of Bool above therefore means that a boolean value takes eight bits to store, and has Integer as its immediate supertype. Currently, only sizes that are multiples of 8 bits are supported. Therefore, boolean values, although they really need just a single bit, cannot be declared to be any smaller than eight bits.
The types Bool, Int8 and Uint8 all have identical representations: they are eight-bit chunks of memory. Since Julia’s type system is nominative, however, they are not interchangeable despite having identical structure. Another fundamental difference between them is that they have different supertypes: Bool‘s direct supertype is Integer, Int8‘s is Signed, and Uint8‘s is Unsigned. All other differences between Bool, Int8, and Uint8 are matters of behavior — the way functions are defined to act when given objects of these types as arguments. This is why a nominative type system is necessary: if structure determined type, which in turn dictates behavior, then it would be impossible to make Bool behave any differently than Int8 or Uint8.
Composite types are called records, structures (structs in C), or objects in various languages. A composite type is a collection of named fields, an instance of which can be treated as a single value. In many languages, composite types are the only kind of user-definable type, and they are by far the most commonly used user-defined type in Julia as well.
In mainstream object oriented languages, such as C++, Java, Python and Ruby, composite types also have named functions associated with them, and the combination is called an “object”. In purer object-oriented languages, such as Python and Ruby, all values are objects whether they are composites or not. In less pure object oriented languages, including C++ and Java, some values, such as integers and floating-point values, are not objects, while instances of user-defined composite types are true objects with associated methods. 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 (see Methods for more information on methods and dispatch). Thus, it would be inappropriate for functions to “belong” to only their first argument. 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.
Since composite types are the most common form of user-defined concrete type, they are simply introduced with the type keyword followed by a block of field names, optionally annotated with types using the :: operator:
type Foo
bar
baz::Int
qux::Float64
end
Fields with no type annotation default to Any, and can accordingly hold any type of value.
New objects of composite type Foo are created by applying the Foo type object like a function to values for its fields:
julia> foo = Foo("Hello, world.", 23, 1.5)
Foo("Hello, world.",23,1.5)
julia> typeof(foo)
Foo
Since the bar field is unconstrained in type, any value will do; the value for baz must be an Int and qux must be a Float64. The signature of the default constructor is taken directly from the field type declarations (Any,Int,Float64), so arguments must match this implied type signature:
julia> Foo((), 23.5, 1)
no method Foo((),Float64,Int64)
You can access the field values of a composite object using the traditional foo.bar notation:
julia> foo.bar
"Hello, world."
julia> foo.baz
23
julia> foo.qux
1.5
You can also change the values as one would expect:
julia> foo.qux = 2
2.0
julia> foo.bar = 1//2
1//2
Composite types with no fields are singletons; there can be only one instance of such types:
type NoFields
end
julia> is(NoFields(), NoFields())
true
The is function confirms that the “two” constructed instances of NoFields are actually one and the same. Singleton types are described in further detail below.
There is much more to say about how instances of composite types are created, but that discussion depends on both Parametric Types and on Methods, and is sufficiently important to be addressed in its own section: Constructors.
It is also possible to define immutable composite types by using the keyword immutable instead of type:
immutable Complex
real::Float64
imag::Float64
end
Such types behave much like other composite types, except that instances of them cannot be modified. Immutable types have several advantages:
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.
A useful way to think about immutable composites is that each instance is associated with specific field values — the field values alone tell you everything about the object. In contrast, a mutable object is like a little container that might hold different values over time, and so is not identified with specific field values. 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.
The three kinds of types discussed in the previous three sections are actually all closely related. They share the same key properties:
Because of these shared properties, these types are internally represented as instances of the same concept, DataType, which is the type of any of these types:
julia> typeof(Real)
DataType
julia> typeof(Int)
DataType
A DataType may be abstract or concrete. If it is concrete, it has a specified size, storage layout, and (optionally) field names. Thus a bits type is a DataType with nonzero size, but no field names. A composite type is a DataType that has field names or is empty (zero size).
Every concrete value in the system is either an instance of some DataType, or is a tuple.
Tuples are an abstraction of the arguments of a function — without the function itself. The salient aspects of a function’s arguments are their order and their types. The type of a tuple of values is the tuple of types of values:
julia> typeof((1,"foo",2.5))
(Int64,ASCIIString,Float64)
Accordingly, a tuple of types can be used anywhere a type is expected:
julia> (1,"foo",2.5) :: (Int64,String,Any)
(1,"foo",2.5)
julia> (1,"foo",2.5) :: (Int64,String,Float32)
ERROR: type: typeassert: expected (Int64,String,Float32), got (Int64,ASCIIString,Float64)
If one of the components of the tuple is not a type, however, you will get an error:
julia> (1,"foo",2.5) :: (Int64,String,3)
ERROR: type: typeassert: expected Type{T<:Top}, got (DataType,DataType,Int64)
Note that the empty tuple () is its own type:
julia> typeof(())
()
Tuple types are covariant in their constituent types, which means that one tuple type is a subtype of another if elements of the first are subtypes of the corresponding elements of the second. For example:
julia> (Int,String) <: (Real,Any)
true
julia> (Int,String) <: (Real,Real)
false
julia> (Int,String) <: (Real,)
false
Intuitively, this corresponds to the type of a function’s arguments being a subtype of the function’s signature (when the signature matches).
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:
julia> IntOrString = Union(Int,String)
Union(Int,String)
julia> 1 :: IntOrString
1
julia> "Hello!" :: IntOrString
"Hello!"
julia> 1.0 :: IntOrString
type error: typeassert: expected Union(Int,String), got Float64
The compilers for many languages have an internal union construct for reasoning about types; Julia simply exposes it to the programmer. The union of no types is the “bottom” type, None:
julia> Union()
None
Recall from the discussion above that None is the abstract type which is the subtype of all other types, and which no object is an instance of. Since a zero-argument Union call has no argument types for objects to be instances of, it should produce a type which no objects are instances of — i.e. None.
An important and powerful feature of Julia’s type system is that it is parametric: types can take parameters, so that type declarations actually introduce a whole family of new types — one for each possible combination of parameter values. There are many languages that support some version of generic programming, wherein data structures and algorithms to manipulate them may be specified without specifying the exact types involved. For example, some form of generic programming exists in ML, Haskell, Ada, Eiffel, C++, Java, C#, F#, and Scala, just to name a few. Some of these languages support true parametric polymorphism (e.g. ML, Haskell, Scala), while others support ad-hoc, template-based styles of generic programming (e.g. C++, Java). With so many different varieties of generic programming and parametric types in various languages, we won’t even attempt to compare Julia’s parametric types to other languages, but will instead focus on explaining Julia’s system in its own right. We will note, however, that because Julia is a dynamically typed language and doesn’t need to make all type decisions at compile time, many traditional difficulties encountered in static parametric type systems can be relatively easily handled.
All declared types (the DataType variety) can be parameterized, with the same syntax in each case. We will discuss them in the following order: first, parametric composite types, then parametric abstract types, and finally parametric bits types.
Type parameters are introduced immediately after the type name, surrounded by curly braces:
type Point{T}
x::T
y::T
end
This declaration defines a new parametric type, Point{T}, holding two “coordinates” of type T. What, one may ask, is T? Well, that’s precisely the point of parametric types: it can be any type at all (or an integer, actually, although here it’s clearly used as a type). Point{Float64} is a concrete type equivalent to the type defined by replacing T in the definition of Point with Float64. Thus, 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:
julia> Point{Float64}
Point{Float64}
julia> Point{String}
Point{String}
The type Point{Float64} is a point whose coordinates are 64-bit floating-point values, while the type Point{String} is a “point” whose “coordinates” are string objects (see Strings). However, Point itself is also a valid type object:
julia> Point
Point{T}
Here the T is the dummy type symbol used in the original declaration of Point. What does Point by itself mean? It is an abstract type that contains all the specific instances Point{Float64}, Point{String}, etc.:
julia> Point{Float64} <: Point
true
julia> Point{String} <: Point
true
Other types, of course, are not subtypes of it:
julia> Float64 <: Point
false
julia> String <: Point
false
Concrete Point types with different values of T are never subtypes of each other:
julia> Point{Float64} <: Point{Int64}
false
julia> Point{Float64} <: Point{Real}
false
This last point is very important:
Even though Float64 <: Real we DO NOT have Point{Float64} <: Point{Real}.
In other words, in the parlance of type theory, Julia’s type parameters are invariant, rather than being covariant (or even contravariant). 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:
The efficiency gained by being able to store Point{Float64} objects with immediate values is magnified enormously in the case of arrays: an Array{Float64} can be stored as a contiguous memory block of 64-bit floating-point values, whereas an Array{Real} must be an array of pointers to individually allocated Real objects — which may well be boxed 64-bit floating-point values, but also might be arbitrarily large, complex objects, which are declared to be implementations of the Real abstract type.
How does one construct a Point object? It is possible to define custom constructors for composite types, which will be discussed in detail in Constructors, but in the absence of any special constructor declarations, there are two default ways of creating new composite objects, one in which the type parameters are explicitly given and the other in which they are implied by the arguments to the object constructor.
Since the type Point{Float64} is a concrete type equivalent to Point declared with Float64 in place of T, it can be applied as a constructor accordingly:
julia> Point{Float64}(1.0,2.0)
Point(1.0,2.0)
julia> typeof(ans)
Point{Float64}
For the default constructor, exactly one argument must be supplied for each field:
julia> Point{Float64}(1.0)
no method Point(Float64,)
julia> Point{Float64}(1.0,2.0,3.0)
no method Point(Float64,Float64,Float64)
The provided arguments need to match the field types exactly, in this case (Float64,Float64), as with all composite type default constructors.
In many cases, it is redundant to provide the type of Point object one wants to construct, since the types of arguments to the constructor call already implicitly provide type information. For that reason, you can also apply Point itself as a constructor, provided that the implied value of the parameter type T is unambiguous:
julia> Point(1.0,2.0)
Point(1.0,2.0)
julia> typeof(ans)
Point{Float64}
julia> Point(1,2)
Point(1,2)
julia> typeof(ans)
Point{Int64}
In the case of Point, the type of T is unambiguously implied if and only if the two arguments to Point have the same type. When this isn’t the case, the constructor will fail with a no method error:
julia> Point(1,2.5)
no method Point(Int64,Float64)
Constructor methods to appropriately handle such mixed cases can be defined, but that will not be discussed until later on in Constructors.
Parametric abstract type declarations declare a collection of abstract types, in much the same way:
abstract Pointy{T}
With this declaration, Pointy{T} is a distinct abstract type for each type or integer value of T. As with parametric composite types, each such instance is a subtype of Pointy:
julia> Pointy{Int64} <: Pointy
true
julia> Pointy{1} <: Pointy
true
Parametric abstract types are invariant, much as parametric composite types are:
julia> Pointy{Float64} <: Pointy{Real}
false
julia> Pointy{Real} <: Pointy{Float64}
false
Much as plain old abstract types serve to create a useful hierarchy of types over concrete types, parametric abstract types serve the same purpose with respect to parametric composite types. We could, for example, have declared Point{T} to be a subtype of Pointy{T} as follows:
type Point{T} <: Pointy{T}
x::T
y::T
end
Given such a declaration, for each choice of T, we have Point{T} as a subtype of Pointy{T}:
julia> Point{Float64} <: Pointy{Float64}
true
julia> Point{Real} <: Pointy{Real}
true
julia> Point{String} <: Pointy{String}
true
This relationship is also invariant:
julia> Point{Float64} <: Pointy{Real}
false
What purpose do parametric abstract types like Pointy serve? Consider if we create a point-like implementation that only requires a single coordinate because the point is on the diagonal line x = y:
type DiagPoint{T} <: Pointy{T}
x::T
end
Now both Point{Float64} and DiagPoint{Float64} are implementations of the Pointy{Float64} abstraction, and similarly for every other possible choice of type T. This allows programming to a common interface shared by all Pointy objects, implemented for both Point and DiagPoint. This cannot be fully demonstrated, however, until we have introduced methods and dispatch in the next section, Methods.
There are situations where it may not make sense for type parameters to range freely over all possible types. In such situations, one can constrain the range of T like so:
abstract Pointy{T<:Real}
With such a declaration, it is acceptable to use any type that is a subtype of Real in place of T, but not types that are not subtypes of Real:
julia> Pointy{Float64}
Pointy{Float64}
julia> Pointy{Real}
Pointy{Real}
julia> Pointy{String}
ERROR: type: Pointy: in T, expected Real, got Type{String}
julia> Pointy{1}
ERROR: type: Pointy: in T, expected Real, got Int64
Type parameters for parametric composite types can be restricted in the same manner:
type Point{T<:Real} <: Pointy{T}
x::T
y::T
end
To give a real-world example of how all this parametric type machinery can be useful, here is the actual definition of Julia’s Rational immutable type (except that we omit the constructor here for simplicity), representing an exact ratio of integers:
immutable Rational{T<:Integer} <: Real
num::T
den::T
end
It only makes sense to take ratios of integer values, so the parameter type T is restricted to being a subtype of Integer, and a ratio of integers represents a value on the real number line, so any Rational is an instance of the Real abstraction.
There is a special kind of abstract parametric type that must be mentioned here: singleton types. For each type, T, the “singleton type” Type{T} is an abstract type whose only instance is the object T. Since the definition is a little difficult to parse, let’s look at some examples:
julia> isa(Float64, Type{Float64})
true
julia> isa(Real, Type{Float64})
false
julia> isa(Real, Type{Real})
true
julia> isa(Float64, Type{Real})
false
In other words, isa(A,Type{B}) is true if and only if A and B 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:
julia> isa(Type{Float64},Type)
true
julia> isa(Float64,Type)
true
julia> isa(Real,Type)
true
Any object that is not a type is not an instance of Type:
julia> isa(1,Type)
false
julia> isa("foo",Type)
false
Until we discuss Parametric Methods and conversions, it is difficult to explain the utility of the singleton type construct, but in short, 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.
A few popular languages have singleton types, including Haskell, Scala and Ruby. In general usage, the term “singleton type” refers to a type whose only instance is a single value. This meaning applies to Julia’s singleton types, but with that caveat that only type objects have singleton types.
Bits types can also be declared parametrically. For example, pointers are represented as boxed bits types which would be declared in Julia like this:
# 32-bit system:
bitstype 32 Ptr{T}
# 64-bit system:
bitstype 64 Ptr{T}
The slightly odd feature of these declarations as compared to typical parametric composite types, is that 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} and Ptr{Int64} are distinct types, even though they have identical representations. And of course, all specific pointer types are subtype of the umbrella Ptr type:
julia> Ptr{Float64} <: Ptr
true
julia> Ptr{Int64} <: Ptr
true
Sometimes it is convenient to introduce a new name for an already expressible type. For such occasions, Julia provides the typealias mechanism. For example, Uint is type aliased to either Uint32 or Uint64 as is appropriate for the size of pointers on the system:
# 32-bit system:
julia> Uint
Uint32
# 64-bit system:
julia> Uint
Uint64
This is accomplished via the following code in base/boot.jl:
if is(Int,Int64)
typealias Uint Uint64
else
typealias Uint Uint32
end
Of course, this depends on what Int is aliased to — but that is pre-defined to be the correct type — either Int32 or Int64.
For parametric types, typealias can be convenient for providing names for cases where some of the parameter choices are fixed. Julia’s arrays have type Array{T,N} where T is the element type and N is the number of array dimensions. For convenience, writing Array{Float64} allows one to specify the element type without specifying the dimension:
julia> Array{Float64,1} <: Array{Float64} <: Array
true
However, there is no way to equally simply restrict just the dimension but not the element type. Yet, one often needs to ensure an object is a vector or a matrix (imposing restrictions on the number of dimensions). For that reason, the following type aliases are provided:
typealias Vector{T} Array{T,1}
typealias Matrix{T} Array{T,2}
Writing Vector{Float64} is equivalent to writing Array{Float64,1}, and the umbrella type Vector has as instances all Array objects where the second parameter — the number of array dimensions — is 1, regardless of what the element type is. In languages where parametric types must always be specified in full, this is not especially helpful, but in Julia, this allows one to write just Matrix for the abstract type including all two-dimensional dense arrays of any element type.
Since types in Julia are themselves objects, ordinary functions can operate on them. Some functions that are particularly useful for working with or exploring types have already been introduced, such as the <: operator, which indicates whether its left hand operand is a subtype of its right hand operand.
The isa function tests if an object is of a given type and returns true or false:
julia> isa(1,Int)
true
julia> isa(1,FloatingPoint)
false
The typeof function, already used throughout the manual in examples, returns the type of its argument. Since, as noted above, types are objects, they also have types, and we can ask what their types are:
julia> typeof(Rational)
DataType
julia> typeof(Union(Real,Float64,Rational))
UnionType
julia> typeof((Rational,None))
(DataType,UnionType)
What if we repeat the process? What is the type of a type of a type? As it happens, types are all composite values and thus all have a type of DataType:
julia> typeof(DataType)
DataType
julia> typeof(UnionType)
DataType
The reader may note that DataType shares with the empty tuple (see above), the distinction of being its own type (i.e. a fixed point of the typeof function). This leaves any number of tuple types recursively built with () and DataType as their only atomic values, which are their own type:
julia> typeof(())
()
julia> typeof(DataType)
DataType
julia> typeof(((),))
((),)
julia> typeof((DataType,))
(DataType,)
julia> typeof(((),DataType))
((),DataType)
All fixed points of the typeof function are like this.
Another operation that applies to some types is super, which reveals a type’s supertype. Only declared types (DataType) have unambiguous supertypes:
julia> super(Float64)
FloatingPoint
julia> super(Number)
Any
julia> super(String)
Any
julia> super(Any)
Any
If you apply super to other type objects (or non-type objects), a “no method” error is raised:
julia> super(Union(Float64,Int64))
no method super(UnionType,)
julia> super(None)
no method super(UnionType,)
julia> super((Float64,Int64))
no method super((DataType,DataType),)
Recall from Functions that a function is an object that maps a tuple of arguments to a return value, or throws an exception if no appropriate value can be returned. It is very common for the same conceptual function or operation to be implemented quite differently for different types of arguments: adding two integers is very different from adding two floating-point numbers, both of which are distinct from adding an integer to a floating-point number. Despite their implementation differences, these operations all fall under the general concept of “addition”. Accordingly, in Julia, these behaviors all belong to a single object: the + function.
To facilitate using many different implementations of the same concept smoothly, functions need not be defined all at once, but can rather be defined piecewise by providing specific behaviors for certain combinations of argument types and counts. A definition of one possible behavior for a function is called a method. Thus far, we have presented only examples of functions defined with a single method, applicable to all types of arguments. However, the signatures of method definitions can be annotated to indicate the types of arguments in addition to their number, and more than a single method definition may be provided. When a function is applied to a particular tuple of arguments, the most specific method applicable to those arguments is applied. Thus, the overall behavior of a function is a patchwork of the behaviors of its various method definitions. If the patchwork is well designed, even though the implementations of the methods may be quite different, the outward behavior of the function will appear seamless and consistent.
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. This is different than traditional object-oriented languages, where dispatch occurs based only on the first argument, which often has a special argument syntax, and is sometimes implied rather than explicitly written as an argument. [1] Using all of a function’s arguments to choose which method should be invoked, rather than just the first, is known as multiple dispatch. Multiple dispatch is particularly useful for mathematical code, where it makes little sense to artificially deem the operations to “belong” to one argument more than any of the others: does the addition operation in x + y belong to x any more than it does to y? The implementation of a mathematical operator generally depends on the types of all of its arguments. Even beyond mathematical operations, however, multiple dispatch ends up being a very powerful and convenient paradigm for structuring and organizing programs.
[1] | In C++ or Java, for example, in a method call like obj.meth(arg1,arg2), the object obj “receives” the method call and is implicitly passed to the method via the this keyword, rather then as an explicit method argument. When the current this object is the receiver of a method call, it can be omitted altogether, writing just meth(arg1,arg2), with this implied as the receiving object. |
Until now, we have, in our examples, defined only functions with a single method having unconstrained argument types. Such functions behave just like they would in traditional dynamically typed languages. Nevertheless, we have used multiple dispatch and methods almost continually without being aware of it: all of Julia’s standard functions and operators, like the aforementioned + function, have many methods defining their behavior over various possible combinations of argument type and count.
When defining a function, one can optionally constrain the types of parameters it is applicable to, using the :: type-assertion operator, introduced in the section on Composite Types:
f(x::Float64, y::Float64) = 2x + y
This function definition applies only to calls where x and y are both values of type Float64:
julia> f(2.0, 3.0)
7.0
Applying it to any other types of arguments will result in a “no method” error:
julia> f(2.0, 3)
no method f(Float64,Int64)
julia> f(float32(2.0), 3.0)
no method f(Float32,Float64)
julia> f(2.0, "3.0")
no method f(Float64,ASCIIString)
julia> f("2.0", "3.0")
no method f(ASCIIString,ASCIIString)
As you can see, the arguments must be precisely of type Float64. Other numeric types, such as integers or 32-bit floating-point values, are not automatically converted to 64-bit floating-point, nor are strings parsed as numbers. Because Float64 is a concrete type and concrete types cannot be subclassed in Julia, such a definition can only be applied to arguments that are exactly of type Float64. It may often be useful, however, to write more general methods where the declared parameter types are abstract:
f(x::Number, y::Number) = 2x - y
julia> f(2.0, 3)
1.0
This method definition applies to any pair of arguments that are instances of Number. They need not be of the same type, so long as they are each numeric values. The problem of handling disparate numeric types is delegated to the arithmetic operations in the expression 2x - y.
To define a function with multiple methods, one simply defines the function multiple times, with different numbers and types of arguments. The first method definition for a function creates the function object, and subsequent method definitions add new methods to the existing function object. The most specific method definition matching the number and types of the arguments will be executed when the function is applied. Thus, the two method definitions above, taken together, define the behavior for f over all pairs of instances of the abstract type Number — but with a different behavior specific to pairs of Float64 values. If one of the arguments is a 64-bit float but the other one is not, then the f(Float64,Float64) method cannot be called and the more general f(Number,Number) method must be used:
julia> f(2.0, 3.0)
7.0
julia> f(2, 3.0)
1.0
julia> f(2.0, 3)
1.0
julia> f(2, 3)
1
The 2x + y definition is only used in the first case, while the 2x - y definition is used in the others. No automatic casting or conversion of function arguments is ever performed: all conversion in Julia is non-magical and completely explicit. Conversion and Promotion, however, shows how clever application of sufficiently advanced technology can be indistinguishable from magic. [Clarke61]
For non-numeric values, and for fewer or more than two arguments, the function f remains undefined, and applying it will still result in a “no method” error:
julia> f("foo", 3)
no method f(ASCIIString,Int64)
julia> f()
no method f()
You can easily see which methods exist for a function by entering the function object itself in an interactive session:
julia> f
Methods for generic function f
f(Float64,Float64)
f(Number,Number)
This output tells us that f is a function object with two methods: one taking two Float64 arguments and one taking arguments of type Number.
In the absence of a type declaration with ::, the type of a method parameter is Any by default, meaning that it is unconstrained since all values in Julia are instances of the abstract type Any. Thus, we can define a catch-all method for f like so:
julia> f(x,y) = println("Whoa there, Nelly.")
julia> f("foo", 1)
Whoa there, Nelly.
This catch-all is less specific than any other possible method definition for a pair of parameter values, so it is only be called on pairs of arguments to which no other method definition applies.
Although it seems a simple concept, multiple dispatch on the types of values is perhaps the single most powerful and central feature of the Julia language. Core operations typically have dozens of methods:
julia> methods(+)
# 92 methods for generic function "+":
+(x::Bool,y::Bool) at bool.jl:38
+(x::Union(Array{Bool,N},SubArray{Bool,N,A<:Array{T,N},I<:(Union(Range1{Int64},Int64,Range{Int64})...,)}),y::Union(Array{Bool,N},SubArray{Bool,N,A<:Array{T,N},I<:(Union(Range1{Int64},Int64,Range{Int64})...,)})) at array.jl:982
+{S,T}(A::Union(Array{S,N},SubArray{S,N,A<:Array{T,N},I<:(Union(Range1{Int64},Int64,Range{Int64})...,)}),B::Union(Array{T,N},SubArray{T,N,A<:Array{T,N},I<:(Union(Range1{Int64},Int64,Range{Int64})...,)})) at array.jl:926
+{T<:Union(Int16,Int8,Int32)}(x::T<:Union(Int16,Int8,Int32),y::T<:Union(Int16,Int8,Int32)) at int.jl:16
+{T<:Union(Uint16,Uint8,Uint32)}(x::T<:Union(Uint16,Uint8,Uint32),y::T<:Union(Uint16,Uint8,Uint32)) at int.jl:20
+(x::Int64,y::Int64) at int.jl:41
+(x::Uint64,y::Uint64) at int.jl:42
+(x::Int128,y::Int128) at int.jl:43
+(x::Uint128,y::Uint128) at int.jl:44
+(a::Float16,b::Float16) at float.jl:129
+(x::Float32,y::Float32) at float.jl:131
+(x::Float64,y::Float64) at float.jl:132
+(z::Complex{T<:Real},w::Complex{T<:Real}) at complex.jl:132
+(x::Real,z::Complex{T<:Real}) at complex.jl:140
+(z::Complex{T<:Real},x::Real) at complex.jl:141
+(x::Rational{T<:Integer},y::Rational{T<:Integer}) at rational.jl:113
+(x::Bool,y::Union(Array{Bool,N},SubArray{Bool,N,A<:Array{T,N},I<:(Union(Range1{Int64},Int64,Range{Int64})...,)})) at array.jl:976
+(x::Union(Array{Bool,N},SubArray{Bool,N,A<:Array{T,N},I<:(Union(Range1{Int64},Int64,Range{Int64})...,)}),y::Bool) at array.jl:979
+(x::Char,y::Char) at char.jl:25
+(x::Char,y::Integer) at char.jl:30
+(x::Integer,y::Char) at char.jl:31
+(x::BigInt,y::BigInt) at gmp.jl:159
+(x::BigInt,c::Uint64) at gmp.jl:195
+(c::Uint64,x::BigInt) at gmp.jl:199
+(c::Unsigned,x::BigInt) at gmp.jl:200
+(x::BigInt,c::Unsigned) at gmp.jl:201
+(x::BigInt,c::Signed) at gmp.jl:202
+(c::Signed,x::BigInt) at gmp.jl:203
+(x::BigFloat,c::Uint64) at mpfr.jl:141
+(c::Uint64,x::BigFloat) at mpfr.jl:145
+(c::Unsigned,x::BigFloat) at mpfr.jl:146
+(x::BigFloat,c::Unsigned) at mpfr.jl:147
+(x::BigFloat,c::Int64) at mpfr.jl:151
+(c::Int64,x::BigFloat) at mpfr.jl:155
+(x::BigFloat,c::Signed) at mpfr.jl:156
+(c::Signed,x::BigFloat) at mpfr.jl:157
+(x::BigFloat,c::Float64) at mpfr.jl:161
+(c::Float64,x::BigFloat) at mpfr.jl:165
+(c::Float32,x::BigFloat) at mpfr.jl:166
+(x::BigFloat,c::Float32) at mpfr.jl:167
+(x::BigFloat,c::BigInt) at mpfr.jl:171
+(c::BigInt,x::BigFloat) at mpfr.jl:175
+(x::BigFloat,y::BigFloat) at mpfr.jl:322
+(x::MathConst{sym},y::MathConst{sym}) at constants.jl:28
+{T<:Number}(x::T<:Number,y::T<:Number) at promotion.jl:178
+(x::Number,y::Number) at promotion.jl:148
+(x::Real,r::Range{T<:Real}) at range.jl:282
+(x::Real,r::Range1{T<:Real}) at range.jl:283
+(r::Ranges{T},x::Real) at range.jl:284
+(r1::Ranges{T},r2::Ranges{T}) at range.jl:296
+(x::Bool) at bool.jl:35
+() at operators.jl:50
+(x::Number) at operators.jl:56
+(a::BigInt,b::BigInt,c::BigInt) at gmp.jl:170
+(a::BigFloat,b::BigFloat,c::BigFloat) at mpfr.jl:333
+(a,b,c) at operators.jl:67
+(a::BigInt,b::BigInt,c::BigInt,d::BigInt) at gmp.jl:176
+(a::BigInt,b::BigInt,c::BigInt,d::BigInt,e::BigInt) at gmp.jl:183
+(a::BigFloat,b::BigFloat,c::BigFloat,d::BigFloat) at mpfr.jl:339
+(a::BigFloat,b::BigFloat,c::BigFloat,d::BigFloat,e::BigFloat) at mpfr.jl:346
+(a,b,c,xs...) at operators.jl:68
+(x::Ptr{T},y::Integer) at pointer.jl:59
+(x::Integer,y::Ptr{T}) at pointer.jl:61
+{T<:Number}(x::AbstractArray{T<:Number,N}) at abstractarray.jl:334
+{T}(A::Number,B::Union(Array{T,N},SubArray{T,N,A<:Array{T,N},I<:(Union(Range1{Int64},Int64,Range{Int64})...,)})) at array.jl:937
+{T}(A::Union(Array{T,N},SubArray{T,N,A<:Array{T,N},I<:(Union(Range1{Int64},Int64,Range{Int64})...,)}),B::Number) at array.jl:944
+{S,T<:Real}(A::Union(Array{S,N},SubArray{S,N,A<:Array{T,N},I<:(Union(Range1{Int64},Int64,Range{Int64})...,)}),B::Ranges{T<:Real}) at array.jl:952
+{S<:Real,T}(A::Ranges{S<:Real},B::Union(Array{T,N},SubArray{T,N,A<:Array{T,N},I<:(Union(Range1{Int64},Int64,Range{Int64})...,)})) at array.jl:961
+(A::BitArray{N},B::BitArray{N}) at bitarray.jl:1143
+(B::BitArray{N},x::Bool) at bitarray.jl:1147
+(B::BitArray{N},x::Number) at bitarray.jl:1150
+(x::Bool,B::BitArray{N}) at bitarray.jl:1154
+(x::Number,B::BitArray{N}) at bitarray.jl:1157
+(A::BitArray{N},B::AbstractArray{T,N}) at bitarray.jl:1395
+(A::AbstractArray{T,N},B::BitArray{N}) at bitarray.jl:1396
+{Tv,Ti}(A::SparseMatrixCSC{Tv,Ti},B::SparseMatrixCSC{Tv,Ti}) at sparse/sparsematrix.jl:409
+{TvA,TiA,TvB,TiB}(A::SparseMatrixCSC{TvA,TiA},B::SparseMatrixCSC{TvB,TiB}) at sparse/sparsematrix.jl:401
+(A::SparseMatrixCSC{Tv,Ti<:Integer},B::Union(Array{T,N},Number)) at sparse/sparsematrix.jl:503
+(A::Union(Array{T,N},Number),B::SparseMatrixCSC{Tv,Ti<:Integer}) at sparse/sparsematrix.jl:504
+(A::SymTridiagonal{T<:Union(Complex{Float64},Float32,Float64,Complex{Float32})},B::SymTridiagonal{T<:Union(Complex{Float64},Float32,Float64,Complex{Float32})}) at linalg/tridiag.jl:50
+(A::Tridiagonal{T},B::Tridiagonal{T}) at linalg/tridiag.jl:151
+(A::Tridiagonal{T},B::SymTridiagonal{T<:Union(Complex{Float64},Float32,Float64,Complex{Float32})}) at linalg/tridiag.jl:164
+(A::SymTridiagonal{T<:Union(Complex{Float64},Float32,Float64,Complex{Float32})},B::Tridiagonal{T}) at linalg/tridiag.jl:165
+(A::Bidiagonal{T},B::Bidiagonal{T}) at linalg/bidiag.jl:76
+(Da::Diagonal{T},Db::Diagonal{T}) at linalg/diagonal.jl:28
+{T}(a::HierarchicalValue{T},b::HierarchicalValue{T}) at pkg/resolve/versionweight.jl:19
+(a::VWPreBuildItem,b::VWPreBuildItem) at pkg/resolve/versionweight.jl:82
+(a::VWPreBuild,b::VWPreBuild) at pkg/resolve/versionweight.jl:120
+(a::VersionWeight,b::VersionWeight) at pkg/resolve/versionweight.jl:164
+(a::FieldValue,b::FieldValue) at pkg/resolve/fieldvalue.jl:41
+(a::Vec2,b::Vec2) at graphics.jl:62
+(bb1::BoundingBox,bb2::BoundingBox) at graphics.jl:128
Multiple dispatch together with the flexible parametric type system give Julia its ability to abstractly express high-level algorithms decoupled from implementation details, yet generate efficient, specialized code to handle each case at run time.
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:
julia> g(x::Float64, y) = 2x + y
julia> g(x, y::Float64) = x + 2y
Warning: New definition g(Any,Float64) is ambiguous with g(Float64,Any).
To fix, define g(Float64,Float64) before the new definition.
julia> g(2.0, 3)
7.0
julia> g(2, 3.0)
8.0
julia> g(2.0, 3.0)
7.0
Here the call g(2.0, 3.0) could be handled by either the g(Float64, Any) or the g(Any, Float64) method, and neither is more specific than the other. 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:
julia> g(x::Float64, y::Float64) = 2x + 2y
julia> g(x::Float64, y) = 2x + y
julia> g(x, y::Float64) = x + 2y
julia> g(2.0, 3)
7.0
julia> g(2, 3.0)
8.0
julia> g(2.0, 3.0)
10.0
To suppress Julia’s warning, the disambiguating method must be defined first, since otherwise the ambiguity exists, if transiently, until the more specific method is defined.
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
same_type(x,y) = false
The first method applies whenever both arguments are of the same concrete type, regardless of what type that is, while the second method acts as a catch-all, covering all other cases. Thus, overall, this defines a boolean function that checks whether its two arguments are of the same type:
julia> same_type(1, 2)
true
julia> same_type(1, 2.0)
false
julia> same_type(1.0, 2.0)
true
julia> same_type("foo", 2.0)
false
julia> same_type("foo", "bar")
true
julia> same_type(int32(1), int64(2))
false
This kind of definition of function behavior by dispatch is quite common — idiomatic, even — in Julia. 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. Here’s an example where the method type parameter T is used as the type parameter to the parametric type Vector{T} in the method signature:
julia> myappend{T}(v::Vector{T}, x::T) = [v..., x]
julia> myappend([1,2,3],4)
4-element Int64 Array:
1
2
3
4
julia> myappend([1,2,3],2.5)
no method myappend(Array{Int64,1},Float64)
julia> myappend([1.0,2.0,3.0],4.0)
[1.0,2.0,3.0,4.0]
julia> myappend([1.0,2.0,3.0],4)
no method myappend(Array{Float64,1},Int64)
As you can see, the type of the appended element must match the element type of the vector it is appended to, or a “no method” error is raised. In the following example, the method type parameter T is used as the return value:
julia> mytypeof{T}(x::T) = T
julia> mytypeof(1)
Int64
julia> mytypeof(1.0)
Float64
Just as you can put subtype constraints on type parameters in type declarations (see Parametric Types), you can also constrain type parameters of methods:
same_type_numeric{T<:Number}(x::T, y::T) = true
same_type_numeric(x::Number, y::Number) = false
julia> same_type_numeric(1, 2)
true
julia> same_type_numeric(1, 2.0)
false
julia> same_type_numeric(1.0, 2.0)
true
julia> same_type_numeric("foo", 2.0)
no method same_type_numeric(ASCIIString,Float64)
julia> same_type_numeric("foo", "bar")
no method same_type_numeric(ASCIIString,ASCIIString)
julia> same_type_numeric(int32(1), int64(2))
false
The same_type_numeric function behaves much like the same_type function defined above, but is only defined for pairs of numbers.
As mentioned briefly in Functions, 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)
Keyword arguments behave quite differently from ordinary positional arguments. In particular, they do not participate in method dispatch. Methods are dispatched based only on positional arguments, with keyword arguments processed after the matching method is identified.
[Clarke61] | Arthur C. Clarke, Profiles of the Future (1961): Clarke’s Third Law. |
Constructors [1] are functions that create new objects — specifically, instances of Composite Types. In Julia, type objects also serve as constructor functions: they create new instances of themselves when applied to an argument tuple as a function. This much was already mentioned briefly when composite types were introduced. For example:
type Foo
bar
baz
end
julia> foo = Foo(1,2)
Foo(1,2)
julia> foo.bar
1
julia> foo.baz
2
For many types, forming new objects by binding their field values together is all that is ever needed to create instances. There are, however, cases where more functionality is required when creating composite objects. Sometimes invariants must be enforced, either by checking arguments or by transforming them. Recursive data structures, especially those that may be self-referential, often cannot be constructed cleanly without first being created in an incomplete state and then altered programmatically to be made whole, as a separate step from object creation. Sometimes, it’s just convenient to be able to construct objects with fewer or different types of parameters than they have fields. Julia’s system for object construction addresses all of these cases and more.
[1] | Nomenclature: while the term “constructor” generally refers to the entire function which constructs objects of a type, it is common to abuse terminology slightly and refer to specific constructor methods as “constructors”. In such situations, it is generally clear from context that the term is used to mean “constructor method” rather than “constructor function”, especially as it is often used in the sense of singling out a particular method of the constructor from all of the others. |
A constructor is just like any other function in Julia in that its overall behavior is defined by the combined behavior of its methods. Accordingly, you can add functionality to a constructor by simply defining new methods. For example, let’s say you want to add a constructor method for Foo objects that takes only one argument and uses the given value for both the bar and baz fields. This is simple:
Foo(x) = Foo(x,x)
julia> Foo(1)
Foo(1,1)
You could also add a zero-argument Foo constructor method that supplies default values for both of the bar and baz fields:
Foo() = Foo(0)
julia> Foo()
Foo(0,0)
Here the zero-argument constructor method calls the single-argument constructor method, which in turn calls the automatically provided two-argument constructor method. For reasons that will become clear very shortly, 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, such as the automatically provided default one.
While outer constructor methods succeed in addressing the problem of providing additional convenience methods for constructing objects, they fail to address the other two use cases mentioned in the introduction of this chapter: enforcing invariants, and allowing construction of self-referential objects. For these problems, one needs inner constructor methods. An inner constructor method is much like an outer constructor method, with two differences:
For example, suppose one wants to declare a type that holds a pair of real numbers, subject to the constraint that the first number is not greater than the second one. One could declare it like this:
type OrderedPair
x::Real
y::Real
OrderedPair(x,y) = x > y ? error("out of order") : new(x,y)
end
Now OrderedPair objects can only be constructed such that x <= y:
julia> OrderedPair(1,2)
OrderedPair(1,2)
julia> OrderedPair(2,1)
out of order
in OrderedPair at none:5
You can still reach in and directly change the field values to violate this invariant, but messing around with an object’s internals uninvited is considered poor form. You (or someone else) can also provide additional outer constructor methods at any later point, but 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. This guarantees that all objects of the declared type must come into existence by a call to one of the inner constructor methods provided with the type, thereby giving some degree of enforcement of a type’s invariants.
Of course, if the type is declared as immutable, then its constructor-provided invariants are fully enforced. This is an important consideration when deciding whether a type should be immutable.
If any inner constructor method is defined, no default constructor method is provided: it is presumed that you have supplied yourself with all the inner constructors you need. The default constructor is equivalent to writing your own inner constructor method that takes all of the object’s fields as parameters (constrained to be of the correct type, if the corresponding field has a type), and passes them to new, returning the resulting object:
type Foo
bar
baz
Foo(bar,baz) = new(bar,baz)
end
This declaration has the same effect as the earlier definition of the Foo type without an explicit inner constructor method. The following two types are equivalent — one with a default constructor, the other with an explicit constructor:
type T1
x::Int64
end
type T2
x::Int64
T2(x::Int64) = new(x)
end
julia> T1(1)
T1(1)
julia> T2(1)
T2(1)
julia> T1(1.0)
no method T1(Float64,)
julia> T2(1.0)
no method T2(Float64,)
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. This separation is typically quite natural.
The final problem which has still not been addressed is construction of self-referential objects, or more generally, recursive data structures. Since the fundamental difficulty may not be immediately obvious, let us briefly explain it. Consider the following recursive type declaration:
type SelfReferential
obj::SelfReferential
end
This type may appear innocuous enough, until one considers how to construct an instance of it. If a is an instance of SelfReferential, then a second instance can be created by the call:
b = SelfReferential(a)
But how does one construct the first instance when no instance exists to provide as a valid value for its obj field? The only solution is to allow creating an incompletely initialized instance of SelfReferential with an unassigned obj field, and using that incomplete instance as a valid value for the obj field of another instance, such as, for example, itself.
To allow for the creation of incompletely initialized objects, Julia allows the new function to be called with fewer than the number of fields that the type has, returning an object with the unspecified fields uninitialized. The inner constructor method can then use the incomplete object, finishing its initialization before returning it. Here, for example, we take another crack at defining the SelfReferential type, with a zero-argument inner constructor returning instances having obj fields pointing to themselves:
type SelfReferential
obj::SelfReferential
SelfReferential() = (x = new(); x.obj = x)
end
We can verify that this constructor works and constructs objects that are, in fact, self-referential:
x = SelfReferential();
julia> is(x, x)
true
julia> is(x, x.obj)
true
julia> is(x, x.obj.obj)
true
Although it is generally a good idea to return a fully initialized object from an inner constructor, incompletely initialized objects can be returned:
type Incomplete
xx
Incomplete() = new()
end
julia> z = Incomplete();
While you are allowed to create objects with uninitialized fields, any access to an uninitialized field is an immediate error:
julia> z.xx
access to undefined reference
This prevents uninitialized fields from propagating throughout a program or forcing programmers to continually check for uninitialized fields, the way they have to check for null values everywhere in Java: if a field is uninitialized and it is used in any way, an error is thrown immediately so no error checking is required. You can also pass incomplete objects to other functions from inner constructors to delegate their completion:
type Lazy
xx
Lazy(v) = complete_me(new(), v)
end
As with incomplete objects returned from constructors, if complete_me or any of its callees try to access the xx field of the Lazy object before it has been initialized, an error will be thrown immediately.
Parametric types add a few wrinkles to the constructor story. Recall from Parametric Types that, by default, instances of parametric composite types can be constructed either with explicitly given type parameters or with type parameters implied by the types of the arguments given to the constructor. Here are some examples:
type Point{T<:Real}
x::T
y::T
end
## implicit T ##
julia> Point(1,2)
Point(1,2)
julia> Point(1.0,2.5)
Point(1.0,2.5)
julia> Point(1,2.5)
no method Point(Int64,Float64)
## explicit T ##
julia> Point{Int64}(1,2)
Point(1,2)
julia> Point{Int64}(1.0,2.5)
no method Point(Float64,Float64)
julia> Point{Float64}(1.0,2.5)
Point(1.0,2.5)
julia> Point{Float64}(1,2)
no method Point(Int64,Int64)
As you can see, for constructor calls with explicit type parameters, the arguments must match that specific type: Point{Int64}(1,2) works, but Point{Int64}(1.0,2.5) does not. When the type is implied by the arguments to the constructor call, as in Point(1,2), then the types of the arguments must agree — otherwise the T cannot be determined — but any pair of real arguments with matching type may be given to the generic Point constructor.
What’s really going on here is that Point, Point{Float64} and Point{Int64} are all different constructor functions. In fact, Point{T} is a distinct constructor function for each type T. Without any explicitly provided inner constructors, the declaration of the composite type Point{T<:Real} automatically provides an inner constructor, Point{T}, for each possible type T<:Real, that behaves just like non-parametric default inner constructors do. It also provides a single general outer Point constructor that takes pairs of real arguments, which must be of the same type. This automatic provision of constructors is equivalent to the following explicit declaration:
type Point{T<:Real}
x::T
y::T
Point(x::T, y::T) = new(x,y)
end
Point{T<:Real}(x::T, y::T) = Point{T}(x,y)
Some features of parametric constructor definitions at work here deserve comment. First, inner constructor declarations always define methods of Point{T} rather than methods of the general Point constructor function. Since Point is not a concrete type, it makes no sense for it to even have inner constructor methods at all. Thus, the inner method declaration Point(x::T, y::T) = new(x,y) provides an inner constructor method for each value of T. It is thus this method declaration that defines the behavior of constructor calls with explicit type parameters like Point{Int64}(1,2) and Point{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, like Point(1,2) and Point(1.0,2.5), work. Since the method declaration restricts the arguments to being of the same type, calls like Point(1,2.5), with arguments of different types, result in “no method” errors.
Suppose we wanted to make the constructor call Point(1,2.5) work by “promoting” the integer value 1 to the floating-point value 1.0. The simplest way to achieve this is to define the following additional outer constructor method:
Point(x::Int64, y::Float64) = Point(convert(Float64,x),y)
This method uses the convert function to explicitly convert x to Float64 and then delegates construction to the general constructor for the case where both arguments are Float64. With this method definition what was previously a “no method” error now successfully creates a point of type Point{Float64}:
julia> Point(1,2.5)
Point(1.0,2.5)
julia> typeof(ans)
Point{Float64}
However, other similar calls still don’t work:
julia> Point(1.5,2)
no method Point(Float64,Int64)
For a much more general way of making all such calls work sensibly, see Conversion and Promotion. At the risk of spoiling the suspense, we can reveal here that the all it takes is the following outer method definition to make all calls to the general Point constructor work as one would expect:
Point(x::Real, y::Real) = Point(promote(x,y)...)
The promote function converts all its arguments to a common type — in this case Float64. With this method definition, the Point constructor promotes its arguments the same way that numeric operators like + do, and works for all kinds of real numbers:
julia> Point(1.5,2)
Point(1.5,2.0)
julia> Point(1,1//2)
Point(1//1,1//2)
julia> Point(1.0,1//2)
Point(1.0,0.5)
Thus, while the implicit type parameter constructors provided by default in Julia are fairly strict, it is possible to make them behave in a more relaxed but sensible manner quite easily. Moreover, since constructors can leverage all of the power of the type system, methods, and multiple dispatch, defining sophisticated behavior is typically quite simple.
Perhaps the best way to tie all these pieces together is to present a real world example of a parametric composite type and its constructor methods. To that end, here is beginning of rational.jl, which implements Julia’s Rational Numbers:
type Rational{T<:Integer} <: Real
num::T
den::T
function Rational(num::T, den::T)
if num == 0 && den == 0
error("invalid rational: 0//0")
end
g = gcd(den, num)
num = div(num, g)
den = div(den, g)
new(num, den)
end
end
Rational{T<:Integer}(n::T, d::T) = Rational{T}(n,d)
Rational(n::Integer, d::Integer) = Rational(promote(n,d)...)
Rational(n::Integer) = Rational(n,one(n))
//(n::Integer, d::Integer) = Rational(n,d)
//(x::Rational, y::Integer) = x.num // (x.den*y)
//(x::Integer, y::Rational) = (x*y.den) // y.num
//(x::Complex, y::Real) = complex(real(x)//y, imag(x)//y)
//(x::Real, y::Complex) = x*y'//real(y*y')
function //(x::Complex, y::Complex)
xy = x*y'
yy = real(y*y')
complex(real(xy)//yy, imag(xy)//yy)
end
The first line — type Rational{T<:Int} <: Real — declares that Rational takes one type parameter of an integer type, and is itself a real type. The field declarations num::T and den::T indicate that the data held in a Rational{T} object are a pair of integers of type T, one representing the rational value’s numerator and the other representing its denominator.
Now things get interesting. Rational has a single inner constructor method which checks that both of num and den aren’t zero and ensures that every rational is constructed in “lowest terms” with a non-negative denominator. This is accomplished by dividing the given numerator and denominator values by their greatest common divisor, computed using the gcd function. Since gcd returns the greatest common divisor of its arguments with sign matching the first argument (den here), after this division the new value of den is guaranteed to be non-negative. Because this is the only inner constructor for Rational, we can be certain that Rational objects are always constructed in this normalized form.
Rational also provides several outer constructor methods for convenience. The first is the “standard” general constructor that infers the type parameter T from the type of the numerator and denominator when they have the same type. The second applies when the given numerator and denominator values have different types: it promotes them to a common type and then delegates construction to the outer constructor for arguments of matching type. The third outer constructor turns integer values into rationals by supplying a value of 1 as the denominator.
Following the outer constructor definitions, we have a number of methods for the // operator, which provides a syntax for writing rationals. Before these definitions, // is a completely undefined operator with only syntax and no meaning. Afterwards, it behaves just as described in Rational Numbers — its entire behavior is defined in these few lines. The first and most basic definition just makes a//b construct a Rational by applying the Rational constructor to a and b when they are integers. When one of the operands of // is already a rational number, we construct a new rational for the resulting ratio slightly differently; this behavior is actually identical to division of a rational with an integer. Finally, applying // to complex integral values creates an instance of Complex{Rational} — a complex number whose real and imaginary parts are rationals:
julia> (1 + 2im)//(1 - 2im)
-3//5 + 4//5im
julia> typeof(ans)
ComplexPair{Rational{Int64}}
julia> ans <: Complex{Rational}
true
Thus, although the // operator usually returns an instance of Rational, if either of its arguments are complex integers, it will return an instance of Complex{Rational} instead. The interested reader should consider perusing the rest of rational.jl: it is short, self-contained, and implements an entire basic Julia type in just a little over a hundred lines of code.
Julia has a system for promoting arguments of mathematical operators to a common type, which has been mentioned in various other sections, including Integers and Floating-Point Numbers, Mathematical Operations and Elementary Functions, Types, and Methods. In this section, we explain how this promotion system works, as well as how to extend it to new types and apply it to functions besides built-in mathematical operators. Traditionally, programming languages fall into two camps with respect to promotion of arithmetic arguments:
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. However, one may observe that applying mathematical operations to a wide variety of mixed argument types is just an extreme case of polymorphic multiple dispatch — something which Julia’s dispatch and type systems are particularly well-suited to handle. “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. The convert 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. The simplest way to understand this function is to see it in action:
julia> x = 12
12
julia> typeof(x)
Int64
julia> convert(Uint8, x)
12
julia> typeof(ans)
Uint8
julia> convert(FloatingPoint, x)
12.0
julia> typeof(ans)
Float64
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> convert(FloatingPoint, "foo")
no method convert(Type{FloatingPoint},ASCIIString)
Some languages consider parsing strings as numbers or formatting numbers as strings to be conversions (many dynamic languages will even perform conversion for you automatically), however Julia does not: even though some strings can be parsed as numbers, most strings are not valid representations of numbers, and only a very limited subset of them are.
To define a new conversion, simply provide a new method for convert. That’s really all there is to it. For example, the method to convert a number to a boolean is simply this:
convert(::Type{Bool}, x::Number) = (x!=0)
The type of the first argument of this method is a singleton type, Type{Bool}, the only instance of which is Bool. Thus, this method is only invoked when the first argument is the type value Bool. When invoked, the method determines whether a numeric value is true or false as a boolean, by comparing it to zero:
julia> convert(Bool, 1)
true
julia> convert(Bool, 0)
false
julia> convert(Bool, 1im)
true
julia> convert(Bool, 0im)
false
The method signatures for conversion methods are often quite a bit more involved than this example, especially for parametric types. The example above is meant to be pedagogical, and is not the actual julia behaviour. This is the actual implementation in julia:
convert{T<:Real}(::Type{T}, z::Complex) = (imag(z)==0 ? convert(T,real(z)) :
throw(InexactError()))
julia> convert(Bool, 1im)
InexactError()
in convert at complex.jl:40
To continue our case study of Julia’s Rational type, here are the conversions declared in rational.jl, right after the declaration of the type and its constructors:
convert{T<:Int}(::Type{Rational{T}}, x::Rational) = Rational(convert(T,x.num),convert(T,x.den))
convert{T<:Int}(::Type{Rational{T}}, x::Int) = Rational(convert(T,x), convert(T,1))
function convert{T<:Int}(::Type{Rational{T}}, x::FloatingPoint, tol::Real)
if isnan(x); return zero(T)//zero(T); end
if isinf(x); return sign(x)//zero(T); end
y = x
a = d = one(T)
b = c = zero(T)
while true
f = convert(T,round(y)); y -= f
a, b, c, d = f*a+c, f*b+d, a, b
if y == 0 || abs(a/b-x) <= tol
return a//b
end
y = 1/y
end
end
convert{T<:Int}(rt::Type{Rational{T}}, x::FloatingPoint) = convert(rt,x,eps(x))
convert{T<:FloatingPoint}(::Type{T}, x::Rational) = convert(T,x.num)/convert(T,x.den)
convert{T<:Int}(::Type{T}, x::Rational) = div(convert(T,x.num),convert(T,x.den))
The initial four convert methods provide conversions to rational types. The first method converts one type of rational to another type of rational by converting the numerator and denominator to the appropriate integer type. The second method does the same conversion for integers by taking the denominator to be 1. The third method implements a standard algorithm for approximating a floating-point number by a ratio of integers to within a given tolerance, and the fourth method applies it, using machine epsilon at the given value as the threshold. In general, one should have a//b == convert(Rational{Int64}, a/b).
The last two convert methods provide conversions from rational types to floating-point and integer types. To convert to floating point, one simply converts both numerator and denominator to that floating point type and then divides. To convert to integer, one can use the div operator for truncated integer division (rounded towards zero).
Promotion refers to converting values of mixed types to a single common type. Although it is not strictly necessary, it is generally implied that the common type to which the values are converted can faithfully represent all of the original values. In this sense, the term “promotion” is appropriate since the values are converted to a “greater” type — i.e. one which can represent all of the input values in a single common type. It is important, however, not to confuse this with object-oriented (structural) super-typing, or Julia’s notion of abstract super-types: 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 a Float64 value, Int32 is not a subtype of Float64.
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 most common use case for promotion is to convert numeric arguments to a common type:
julia> promote(1, 2.5)
(1.0,2.5)
julia> promote(1, 2.5, 3)
(1.0,2.5,3.0)
julia> promote(2, 3//4)
(2//1,3//4)
julia> promote(1, 2.5, 3, 3//4)
(1.0,2.5,3.0,0.75)
julia> promote(1.5, im)
(1.5 + 0.0im,0.0 + 1.0im)
julia> promote(1 + 2im, 3//4)
(1//1 + 2//1im,3//4 + 0//1im)
Integer values are promoted to the largest type of the integer values. Floating-point values are promoted to largest of the floating-point types. Mixtures of integers and floating-point values are promoted to a floating-point type big enough to hold all the values. Integers mixed with rationals are promoted to rationals. Rationals mixed with floats are promoted to floats. Complex values mixed with real values are promoted to the appropriate kind of complex value.
That is really all there is to using promotions. The rest is just a matter of clever application, the most typical “clever” application being the definition of catch-all methods for numeric operations like the arithmetic operators +, -, * and /. Here are some of the the catch-all method definitions given in promotion.jl:
+(x::Number, y::Number) = +(promote(x,y)...)
-(x::Number, y::Number) = -(promote(x,y)...)
*(x::Number, y::Number) = *(promote(x,y)...)
/(x::Number, y::Number) = /(promote(x,y)...)
These method definitions say that in the absence of more specific rules for adding, subtracting, multiplying and dividing pairs of numeric values, promote the values to a common type and then try again. That’s all there is to it: nowhere else does one ever need to worry about promotion to a common numeric type for arithmetic operations — it just happens automatically. There are definitions of catch-all promotion methods for a number of other arithmetic and mathematical functions in promotion.jl, but beyond that, there are hardly any calls to promote required in the Julia standard library. 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. For example, recall that rational.jl provides the following outer constructor method:
Rational(n::Integer, d::Integer) = Rational(promote(n,d)...)
This allows calls like the following to work:
julia> Rational(int8(15),int32(-5))
-3//1
julia> typeof(ans)
Rational{Int64}
For most user-defined types, it is better practice to require programmers to supply the expected types to constructor functions explicitly, but sometimes, especially for numeric problems, it can be convenient to do promotion automatically.
Although one could, in principle, define methods for the promote function directly, this would require many redundant definitions for all possible permutations of argument types. Instead, the behavior of promote is defined in terms of an auxiliary function called promote_rule, which one can provide methods for. The promote_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. Thus, by defining the rule:
promote_rule(::Type{Float64}, ::Type{Float32} ) = Float64
one declares that when 64-bit and 32-bit floating-point values are promoted together, they should be promoted to 64-bit floating-point. The promotion type does not need to be one of the argument types, however; the following promotion rules both occur in Julia’s standard library:
promote_rule(::Type{Uint8}, ::Type{Int8}) = Int
promote_rule(::Type{Char}, ::Type{Uint8}) = Int32
As a general rule, Julia promotes integers to Int during computation order to avoid overflow. In the latter case, the result type is Int32 since Int32 is large enough to contain all possible Unicode code points, and numeric operations on characters always result in plain old integers unless explicitly cast back to characters (see Characters). Also note that one does not need to define both promote_rule(::Type{A}, ::Type{B}) and promote_rule(::Type{B}, ::Type{A}) — the symmetry is implied by the way promote_rule is used in the promotion process.
The promote_rule function is used as a building block to define a second function called promote_type, which, given any number of type objects, returns the common type to which those values, as arguments to promote 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 use promote_type:
julia> promote_type(Int8, Uint16)
Int64
Internally, promote_type is used inside of promote to determine what type argument values should be converted to for promotion. It can, however, be useful in its own right. The curious reader can read the code in promotion.jl, which defines the complete promotion mechanism in about 35 lines.
Finally, we finish off our ongoing case study of Julia’s rational number type, which makes relatively sophisticated use of the promotion mechanism with the following promotion rules:
promote_rule{T<:Int}(::Type{Rational{T}}, ::Type{T}) = Rational{T}
promote_rule{T<:Int,S<:Int}(::Type{Rational{T}}, ::Type{S}) = Rational{promote_type(T,S)}
promote_rule{T<:Int,S<:Int}(::Type{Rational{T}}, ::Type{Rational{S}}) = Rational{promote_type(T,S)}
promote_rule{T<:Int,S<:FloatingPoint}(::Type{Rational{T}}, ::Type{S}) = promote_type(T,S)
The first rule asserts that promotion of a rational number with its own numerator/denominator type, simply promotes to itself. The second rule says that promoting a rational number with any other integer type promotes to a rational type whose numerator/denominator type is the result of promotion of its numerator/denominator type with the other integer type. The third rule applies the same logic to two different types of rational numbers, resulting in a rational of the promotion of their respective numerator/denominator types. The fourth and final rule dictates that promoting a rational with a float results in the same type as promoting the numerator/denominator type with the float.
This small handful of promotion rules, together with the conversion methods discussed above, are sufficient to make rational numbers interoperate completely naturally with all of Julia’s other numeric types — integers, floating-point numbers, and complex numbers. By providing appropriate conversion methods and promotion rules in the same manner, any user-defined numeric type can interoperate just as naturally with Julia’s predefined numerics.
Modules in Julia are separate global variable workspaces. They are delimited syntactically, inside module Name ... end. Modules allow you to create top-level definitions without worrying about name conflicts when your code is used together with somebody else’s. Within a module, you can control which names from other modules are visible (via importing), and specify which of your names are intended to be public (via exporting).
The following example demonstrates the major features of modules. It is not meant to be run, but is shown for illustrative purposes:
module MyModule
using Lib
export MyType, foo
type MyType
x
end
bar(x) = 2x
foo(a::MyType) = bar(a.x) + 1
import Base.show
show(io, a::MyType) = print(io, "MyType $(a.x)")
end
Note that the style is not to indent the body of the module, since that would typically lead to whole files being indented.
This module defines a type MyType, and two functions. Function foo and type MyType are exported, and so will be available for importing into other modules. Function bar is private to MyModule.
The statement using Lib means that a module called Lib will be available for resolving names as needed. When a global variable is encountered that has no definition in the current module, the system will search for it in Lib and import it if it is found there. This means that all uses of that global within the current module will resolve to the definition of that variable in Lib.
Once a variable is imported this way (or, equivalently, with the import keyword), a module may not create its own variable with the same name. Imported variables are read-only; assigning to a global variable always affects a variable owned by the current module, or else raises an error.
Method definitions are a bit special: they do not search modules named in using statements. The definition function foo() creates a new foo in the current module, unless foo has already been imported from elsewhere. For example, in MyModule above we wanted to add a method to the standard show function, so we had to write import Base.show.
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
include("file1.jl")
include("file2.jl")
end
Including the same code in different modules provides mixin-like behavior. One could use this to run the same code with different base definitions, for example testing code by running it with “safe” versions of some operators:
module Normal
include("mycode.jl")
end
module Testing
include("safe_operators.jl")
include("mycode.jl")
end
There are three important standard modules: Main, Core, and Base.
Main is the top-level module, and Julia starts with Main set as the current module. Variables defined at the prompt go in Main, and whos() lists variables in Main.
Core contains all identifiers considered “built in” to the language, i.e. part of the core language and not libraries. Every module implicitly specifies using Core, since you can’t do anything without those definitions.
Base is the standard library (the contents of base/). All modules implicitly contain using Base, since this is needed in the vast majority of cases.
In addition to using Base, all operators are explicitly imported, since one typically wants to extend operators rather than creating entirely new definitions of them. A module also automatically contains a definition of the eval function, which evaluates expressions within the context of that module.
If these definitions are not wanted, modules can be defined using the keyword baremodule instead. In terms of baremodule, a standard module looks like this:
baremodule Mod
using Base
importall Base.Operators
eval(x) = Core.eval(Mod, x)
eval(m,x) = Core.eval(m, x)
...
end
Given the statement using Foo, the system looks for Foo within Main. If the module does not exist, the system attempts to require("Foo"), which typically results in loading code from an installed package.
However, some modules contain submodules, which means you sometimes need to access a module that is not directly available in Main. There are two ways to do this. The first is to use an absolute path, for example using Base.Sort. The second is to use a relative path, which makes it easier to import submodules of the current module or any of its enclosing modules:
module Parent
module Utils
...
end
using .Utils
...
end
Here module Parent contains a submodule Utils, and code in Parent wants the contents of Utils to be visible. This is done by starting the using path with a period. Adding more leading periods moves up additional levels in the module hierarchy. For example using ..Utils would look for Utils in Parent‘s enclosing module rather than in Parent itself.
If a name is qualified (e.g. Base.sin), then it can be accessed even if it is not exported. This is often useful when debugging.
Macros must be exported if they are intended to be used outside their defining module. Macro names are written with @ in import and export statements, e.g. import Mod.@mac.
The syntax M.x = y does not work to assign a global in another module; global assignment is always module-local.
A variable can be “reserved” for the current module without assigning to it by declaring it as global x at the top level. This can be used to prevent name conflicts for globals initialized after load time.
The strongest legacy of Lisp in the Julia language is its metaprogramming support. Like Lisp, Julia is homoiconic: it represents its own code as a data structure of the language itself. Since code is represented by objects that can be created and manipulated from within the language, it is possible for a program to transform and generate its own code. This allows sophisticated code generation without extra build steps, and also allows true Lisp-style macros, as compared to preprocessor “macro” systems, like that of C and C++, that perform superficial textual manipulation as a separate pass before any real parsing or interpretation occurs. Another aspect of metaprogramming is reflection: the ability of a running program to dynamically discover properties of itself. Reflection emerges naturally from the fact that all data types and code are represented by normal Julia data structures, so the structure of the program and its types can be explored programmatically just like any other data.
Julia code is represented as a syntax tree built out of Julia data structures of type Expr. This makes it easy to construct and manipulate Julia code from within Julia, without generating or parsing source text. Here is the definition of the Expr type:
type Expr
head::Symbol
args::Array{Any,1}
typ
end
The head is a symbol identifying the kind of expression, and args is an array of subexpressions, which may be symbols referencing the values of variables at evaluation time, may be nested Expr objects, or may be actual values of objects. The typ field is used by type inference to store type annotations, and can generally be ignored.
There is special syntax for “quoting” code (analogous to quoting strings) that makes it easy to create expression objects without explicitly constructing Expr objects. There are two forms: a short form for inline expressions using : followed by a single expression, and a long form for blocks of code, enclosed in quote ... end. Here is an example of the short form used to quote an arithmetic expression:
julia> ex = :(a+b*c+1)
+(a,*(b,c),1)
julia> typeof(ex)
Expr
julia> ex.head
call
julia> typeof(ans)
Symbol
julia> ex.args
4-element Any Array:
+
a
:(*(b,c))
1
julia> typeof(ex.args[1])
Symbol
julia> typeof(ex.args[2])
Symbol
julia> typeof(ex.args[3])
Expr
julia> typeof(ex.args[4])
Int64
Expressions provided by the parser generally only have symbols, other expressions, and literal values as their args, whereas expressions constructed by Julia code can easily have arbitrary run-time values without literal forms as args. In this specific example, + and a are symbols, *(b,c) is a subexpression, and 1 is a literal 64-bit signed integer. Here’s an example of the longer expression quoting form:
julia> quote
x = 1
y = 2
x + y
end
begin
x = 1
y = 2
+(x,y)
end
When the argument to : is just a symbol, a Symbol object results instead of an Expr:
julia> :foo
foo
julia> typeof(ans)
Symbol
In the context of an expression, symbols are used to indicate access to variables, and when an expression is evaluated, a symbol evaluates to the value bound to that symbol in the appropriate scope.
Sometimes extra parentheses around the argument to : are needed to avoid ambiguity in parsing.:
julia> :(:)
:(:)
julia> :(::)
:(::)
Symbols can also be created using the symbol function, which takes a character or string as its argument:
julia> symbol('\'')
:'
julia> symbol("'")
:'
Given an expression object, one can cause Julia to evaluate (execute) it at the top level scope — i.e. in effect like loading from a file or typing at the interactive prompt — using the eval function:
julia> :(1 + 2)
+(1,2)
julia> eval(ans)
3
julia> ex = :(a + b)
+(a,b)
julia> eval(ex)
a not defined
julia> a = 1; b = 2;
julia> eval(ex)
3
Expressions passed to eval are not limited to returning values — they can also have side-effects that alter the state of the top-level evaluation environment:
julia> ex = :(x = 1)
x = 1
julia> x
x not defined
julia> eval(ex)
1
julia> x
1
Here, the evaluation of an expression object causes a value to be assigned to the top-level variable x.
Since expressions are just Expr objects which can be constructed programmatically and then evaluated, one can, from within Julia code, dynamically generate arbitrary code which can then be run using eval. Here is a simple example:
julia> a = 1;
julia> ex = Expr(:call, :+,a,:b)
:(+(1,b))
julia> a = 0; b = 2;
julia> eval(ex)
3
The value of a is used to construct the expression ex which applies the + function to the value 1 and the variable b. Note the important distinction between the way a and b are used:
Constructing Expr objects like this is powerful, but somewhat tedious and ugly. Since the Julia parser is already excellent at producing expression objects, Julia allows “splicing” or interpolation of expression objects, prefixed with $, into quoted expressions, written using normal syntax. The above example can be written more clearly and concisely using interpolation:
julia> a = 1;
1
julia> ex = :($a + b)
:(+(1,b))
This syntax is automatically rewritten to the form above where we explicitly called Expr. The use of $ for expression interpolation is intentionally reminiscent of string interpolation and command interpolation. Expression interpolation allows convenient, readable programmatic construction of complex Julia expressions.
When a significant amount of repetitive boilerplate code is required, it is common to generate it programmatically to avoid redundancy. In most languages, this requires an extra build step, and a separate program to generate the repetitive code. In Julia, expression interpolation and eval allow such code generation to take place in the normal course of program execution. For example, the following code defines a series of operators on three arguments in terms of their 2-argument forms:
for op = (:+, :*, :&, :|, :$)
eval(quote
($op)(a,b,c) = ($op)(($op)(a,b),c)
end)
end
In this manner, Julia acts as its own preprocessor, and allows code generation from inside the language. The above code could be written slightly more tersely using the : prefix quoting form:
for op = (:+, :*, :&, :|, :$)
eval(:(($op)(a,b,c) = ($op)(($op)(a,b),c)))
end
This sort of in-language code generation, however, using the eval(quote(...)) pattern, is common enough that Julia comes with a macro to abbreviate this pattern:
for op = (:+, :*, :&, :|, :$)
@eval ($op)(a,b,c) = ($op)(($op)(a,b),c)
end
The @eval macro rewrites this call to be precisely equivalent to the above longer versions. For longer blocks of generated code, the expression argument given to @eval can be a block:
@eval begin
# multiple lines
end
Interpolating into an unquoted expression is not supported and will cause a compile-time error:
julia> $a + b
unsupported or misplaced expression $
Macros are the analogue of functions for expression generation at compile time: they allow the programmer to automatically generate expressions by transforming zero or more argument expressions into a single result expression, which then takes the place of the macro call in the final syntax tree. Macros are invoked with the following general syntax:
@name expr1 expr2 ...
@name(expr1, expr2, ...)
Note the distinguishing @ before the macro name and the lack of commas between the argument expressions in the first form, and the lack of whitespace after @name in the second form. The two styles should not be mixed. For example, the following syntax is different from the examples above; it passes the tuple (expr1, expr2, ...) as one argument to the macro:
@name (expr1, expr2, ...)
Before the program runs, this statement will be replaced with the result of calling an expander function for name on the expression arguments. Expanders are defined with the macro keyword:
macro name(expr1, expr2, ...)
...
end
Here, for example, is the definition of Julia’s @assert macro (see error.jl):
macro assert(ex)
:($ex ? nothing : error("Assertion failed: ", $(string(ex))))
end
This macro can be used like this:
julia> @assert 1==1.0
julia> @assert 1==0
Assertion failed: 1==0
Macro calls are expanded so that the above calls are precisely equivalent to writing:
1==1.0 ? nothing : error("Assertion failed: ", "1==1.0")
1==0 ? nothing : error("Assertion failed: ", "1==0")
That is, in the first call, the expression :(1==1.0) is spliced into the test condition slot, while the value of string(:(1==1.0)) is spliced into the assertion message slot. The entire expression, thus constructed, is placed into the syntax tree where the @assert macro call occurs. Therefore, if the test expression is true when evaluated, the entire expression evaluates to nothing, whereas if the test expression is false, an error is raised indicating the asserted expression that was false. Notice that it would not be possible to write this as a function, since only the value of the condition and not the expression that computed it would be available.
The @assert example also shows how macros can include a quote block, which allows for convenient manipulation of expressions inside the macro body.
An issue that arises in more complex macros is that of hygiene. In short, Julia must ensure that variables introduced and used by macros do not accidentally clash with the variables used in code interpolated into those macros. Another concern arises from the fact that a macro may be called in a different module from where it was defined. In this case we need to ensure that all global variables are resolved to the correct module.
To demonstrate these issues, let us consider writing a @time macro that takes an expression as its argument, records the time, evaluates the expression, records the time again, prints the difference between the before and after times, and then has the value of the expression as its final value. The macro might look like this:
macro time(ex)
quote
local t0 = time()
local val = $ex
local t1 = time()
println("elapsed time: ", t1-t0, " seconds")
val
end
end
Here, we want t0, t1, and val to be private temporary variables, and we want time to refer to the time function in the standard library, not to any time variable the user might have (the same applies to println). Imagine the problems that could occur if the user expression ex also contained assignments to a variable called t0, or defined its own time variable. We might get errors, or mysteriously incorrect behavior.
Julia’s macro expander solves these problems in the following way. First, variables within a macro result are classified as either local or global. A variable is considered local if it is assigned to (and not declared global), declared local, or used as a function argument name. Otherwise, it is considered global. Local variables are then renamed to be unique (using the gensym function, which generates new symbols), and global variables are resolved within the macro definition environment. Therefore both of the above concerns are handled; the macro’s locals will not conflict with any user variables, and time and println will refer to the standard library definitions.
One problem remains however. Consider the following use of this macro:
module MyModule
import Base.@time
time() = ... # compute something
@time time()
end
Here the user expression ex is a call to time, but not the same time function that the macro uses. It clearly refers to MyModule.time. Therefore we must arrange for the code in ex to be resolved in the macro call environment. This is done by “escaping” the expression with the esc function:
macro time(ex)
...
local val = $(esc(ex))
...
end
An expression wrapped in this manner is left alone by the macro expander and simply pasted into the output verbatim. Therefore it will be resolved in the macro call environment.
This escaping mechanism can be used to “violate” hygiene when necessary, in order to introduce or manipulate user variables. For example, the following macro sets x to zero in the call environment:
macro zerox()
esc(:(x = 0))
end
function foo()
x = 1
@zerox
x # is zero
end
This kind of manipulation of variables should be used judiciously, but is occasionally quite handy.
Recall from Strings that string literals prefixed by an identifier are called non-standard string literals, and can have different semantics than un-prefixed string literals. For example:
Perhaps surprisingly, these behaviors are not hard-coded into the Julia parser or compiler. Instead, they are custom behaviors provided by a general mechanism that anyone can use: prefixed string literals are parsed as calls to specially-named macros. For example, the regular expression macros is just the following:
macro r_str(p)
Regex(p)
end
That’s all. This macro says that the literal contents of the string literal r"^\s*(?:#|$)" should be passed to the @r_str macro and the result of that expansion should be placed in the syntax tree where the string literal occurs. In other words, the expression r"^\s*(?:#|$)" is equivalent to placing the following object directly into the syntax tree:
Regex("^\\s*(?:#|\$)")
Not only is the string literal form shorter and far more convenient, but it is also more efficient: since the regular expression is compiled and the Regex object is actually created when the code is compiled, the compilation occurs only once, rather than every time the code is executed. Consider if the regular expression occurs in a loop:
for line = lines
m = match(r"^\s*(?:#|$)", line)
if m.match == nothing
# non-comment
else
# comment
end
end
Since the regular expression r"^\s*(?:#|$)" is compiled and inserted into the syntax tree when this code is parsed, the expression is only compiled once instead of each time the loop is executed. In order to accomplish this without macros, one would have to write this loop like this:
re = Regex("^\\s*(?:#|\$)")
for line = lines
m = match(re, line)
if m.match == nothing
# non-comment
else
# comment
end
end
Moreover, if the compiler could not determine that the regex object was constant over all loops, certain optimizations might not be possible, making this version still less efficient than the more convenient literal form above. Of course, there are still situations where the non-literal form is more convenient: if one needs to interpolate a variable into the regular expression, has to take this more verbose approach; in cases where the regular expression pattern itself is dynamic, potentially changing upon each loop iteration, a new regular expression object must be constructed on each iteration. The vast majority of use cases, however, one does not construct regular expressions dynamically, depending on run-time data. In this majority of cases, the ability to write regular expressions as compile-time values is, well, invaluable.
The mechanism for user-defined string literals is deeply, profoundly powerful. Not only are Julia’s non-standard literals implemented using it, but also the command literal syntax (`echo "Hello, $person"`) is implemented with the following innocuous-looking macro:
macro cmd(str)
:(cmd_gen($shell_parse(str)))
end
Of course, a large amount of complexity is hidden in the functions used in this macro definition, but they are just functions, written entirely in Julia. You can read their source and see precisely what they do — and all they do is construct expression objects to be inserted into your program’s syntax tree.
In addition to the syntax-level introspection utilized in metaprogramming, Julia provides several other runtime reflection capabilities.
Type fields The names of data type fields (or module members) may be interrogated using the names command. For example, given the following type:
type Point
x::FloatingPoint
y
end
names(Point) will return the array: Any[ :x :y ]. Note that the type of each field in a Point is stored in the types field of the Point object:
julia> typeof(Point)
DataType
julia> Point.types
(FloatingPoint,Any)
Subtypes The direct subtypes of any DataType may be listed using subtypes(t::DataType). For example, the abstract DataType FloatingPoint has four (concrete) subtypes:
julia> subtypes(FloatingPoint)
5-element Array{Any,1}:
BigFloat
Float16
Float32
Float64
Any abstract subtype will also be included in this list, but further subtypes thereof will not; recursive applications of subtypes allow to build the full type tree.
Type internals The internal representation of types is critically important when interfacing with C code. isbits(T::DataType) returns true if T is stored with C-compatible aligment. The offsets of each field may be listed using fieldoffsets(T::DataType).
Function methods The methods of any function may be listed using methods(f::Function).
Function representations Functions may be introspected at several levels of representation. The lowered form of a function is available using code_lowered(f::Function, (Args...)), and the type-inferred lowered form is available using code_typed(f::Function, (Args...)).
Closer to the machine, the LLVM Intermediate Representation of a function is printed by code_llvm(f::Function, (Args...)), and finally the resulting assembly instructions (after JIT’ing step) are available using code_native(f::Function, (Args...).
Julia, like most technical computing languages, provides a first-class array implementation. Most technical computing languages pay a lot of attention to their array implementation at the expense of other containers. Julia does not treat arrays in any special way. 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.
An array is a collection of objects stored in a multi-dimensional grid. In the most general case, an array may contain objects of type Any. For most computational purposes, arrays should contain objects of a more specific type, such as Float64 or Int32.
In general, unlike many other technical computing languages, Julia does not expect programs to be written in a vectorized style for performance. Julia’s compiler uses type inference and generates optimized code for scalar array indexing, allowing programs to be written in a style that is convenient and readable, without sacrificing performance, and using less memory at times.
In Julia, all arguments to functions are passed by reference. Some technical computing languages pass arrays by value, and this is convenient in many cases. 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 behaviour, should take care to create a copy of inputs that it may modify.
Function | Description |
---|---|
eltype(A) | the type of the elements contained in A |
length(A) | the number of elements in A |
ndims(A) | the number of dimensions of A |
nnz(A) | the number of nonzero values in A |
size(A) | a tuple containing the dimensions of A |
size(A,n) | the size of A in a particular dimension |
stride(A,k) | the stride (linear index distance between adjacent elements) along dimension k |
strides(A) | a tuple of the strides in each dimension |
Many functions for constructing and initializing arrays are provided. In the following list of such functions, calls with a dims... argument can either take a single tuple of dimension sizes or a series of dimension sizes passed as a variable number of arguments.
Function | Description |
---|---|
Array(type, dims...) | an uninitialized dense array |
cell(dims...) | an uninitialized cell array (heterogeneous array) |
zeros(type, dims...) | an array of all zeros of specified type |
ones(type, dims...) | an array of all ones of specified type |
trues(dims...) | a Bool array with all values true |
falses(dims...) | a Bool array with all values false |
reshape(A, dims...) | an array with the same data as the given array, but with different dimensions. |
copy(A) | copy A |
deepcopy(A) | copy A, recursively copying its elements |
similar(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 of A if omitted. |
reinterpret(type, A) | an array with the same binary data as the given array, but with the specified element type |
rand(dims) | Array of Float64s with random, iid[#]_ and uniformly distributed values in [0,1) |
randn(dims) | Array of Float64s with random, iid and standard normally distributed random values |
eye(n) | n-by-n identity matrix |
eye(m, n) | m-by-n identity matrix |
linspace(start, stop, n) | vector of n linearly-spaced elements from start to stop |
fill!(A, x) | fill the array A with value x |
[1] | iid, independently and identically distributed. |
Comprehensions provide a general and powerful way to construct arrays. Comprehension syntax is similar to set construction notation in mathematics:
A = [ F(x,y,...) for x=rx, y=ry, ... ]
The meaning of this form is that F(x,y,...) is evaluated with the variables x, y, etc. taking on each value in their given list of values. Values can be specified as any iterable object, but will commonly be ranges like 1:n or 2:(n-1), or explicit arrays of values like [1.2, 3.4, 5.7]. The result is an N-d dense array with dimensions that are the concatenation of the dimensions of the variable ranges rx, ry, etc. and each F(x,y,...) evaluation returns a scalar.
The following example computes a weighted average of the current element and its left and right neighbor along a 1-d grid.
julia> const x = rand(8)
8-element Float64 Array:
0.276455
0.614847
0.0601373
0.896024
0.646236
0.143959
0.0462343
0.730987
julia> [ 0.25*x[i-1] + 0.5*x[i] + 0.25*x[i+1] for i=2:length(x)-1 ]
6-element Float64 Array:
0.391572
0.407786
0.624605
0.583114
0.245097
0.241854
Note
In the above example, x is declared as constant because type inference in Julia does not work as well on non-constant global variables.
The resulting array type is inferred from the expression; in order to control the type explicitly, the type can be prepended to the comprehension. For example, in the above example we could have avoided declaring x as constant, and ensured that the result is of type Float64 by writing:
Float64[ 0.25*x[i-1] + 0.5*x[i] + 0.25*x[i+1] for i=2:length(x)-1 ]
Using curly brackets instead of square brackets is a shorthand notation for an array of type Any:
julia> { i/2 for i = 1:3 }
3-element Any Array:
0.5
1.0
1.5
The general syntax for indexing into an n-dimensional array A is:
X = A[I_1, I_2, ..., I_n]
where each I_k may be:
The result X generally has dimensions (length(I_1), length(I_2), ..., length(I_n)), with location (i_1, i_2, ..., i_n) of X containing the value A[I_1[i_1], I_2[i_2], ..., I_n[i_n]]. Trailing dimensions indexed with scalars are dropped. For example, the dimensions of A[I, 1] will be (length(I),). 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)
Example:
julia> x = reshape(1:16, 4, 4)
4x4 Int64 Array
1 5 9 13
2 6 10 14
3 7 11 15
4 8 12 16
julia> x[2:3, 2:end-1]
2x2 Int64 Array
6 10
7 11
The general syntax for assigning values in an n-dimensional array A is:
A[I_1, I_2, ..., I_n] = X
where each I_k may be:
If X is an array, its size must be (length(I_1), length(I_2), ..., length(I_n)), and the value in location i_1, i_2, ..., i_n of A is overwritten with the value X[I_1[i_1], I_2[i_2], ..., I_n[i_n]]. If X is not an array, its value is written to all referenced locations of A.
A boolean vector used as an index behaves as in getindex (it is first transformed with find).
Index assignment syntax is equivalent to a call to setindex!:
setindex!(A, X, I_1, I_2, ..., I_n)
Example:
julia> x = reshape(1:9, 3, 3)
3x3 Int64 Array
1 4 7
2 5 8
3 6 9
julia> x[1:2, 2:3] = -1
3x3 Int64 Array
1 -1 -1
2 -1 -1
3 6 9
Arrays can be concatenated along any dimension using the following functions:
Function | Description |
---|---|
cat(k, A...) | concatenate input n-d arrays along the dimension k |
vcat(A...) | shorthand for cat(1, A...) |
hcat(A...) | shorthand for cat(2, A...) |
hvcat(A...) |
Concatenation operators may also be used for concatenating arrays:
Expression | Calls |
---|---|
[A B C ...] | hcat |
[A, B, C, ...] | vcat |
[A B; C D; ...] | hvcat |
The following operators are supported for arrays. In case of binary operators, the dot (element-wise) version of the operator should be used when both inputs are non-scalar, and any version of the operator may be used if one of the inputs is a scalar.
The following built-in functions are also vectorized, whereby the functions act element-wise:
abs abs2 angle cbrt
airy airyai airyaiprime airybi airybiprime airyprime
acos acosh asin asinh atan atan2 atanh
acsc acsch asec asech acot acoth
cos cosh sin sinh tan tanh sinc cosc
csc csch sec sech cot coth
acosd asind atand asecd acscd acotd
cosd sind tand secd cscd cotd
besselh besseli besselj besselj0 besselj1 besselk bessely bessely0 bessely1
exp erf erfc erfinv erfcinv exp2 expm1
beta dawson digamma erfcx erfi
exponent eta zeta gamma
hankelh1 hankelh2
ceil floor round trunc
iceil ifloor iround itrunc
isfinite isinf isnan
lbeta lfact lgamma
log log10 log1p log2
copysign max min significand
sqrt hypot
Furthermore, 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 to be the most general possible) and the name of the function to vectorize. Here is a simple example:
julia> square(x) = x^2
square (generic function with 1 method)
julia> @vectorize_1arg Number square
square (generic function with 4 methods)
julia> methods(square)
# 4 methods for generic function "square":
square{T<:Number}(x::AbstractArray{T<:Number,1}) at operators.jl:236
square{T<:Number}(x::AbstractArray{T<:Number,2}) at operators.jl:237
square{T<:Number}(x::AbstractArray{T<:Number,N}) at operators.jl:239
square(x) at none:1
julia> square([1 2 4; 5 6 7])
2x3 Array{Int64,2}:
1 4 16
25 36 49
It is sometimes useful to perform element-by-element binary operations on arrays of different sizes, such as adding a vector to each column of a matrix. An inefficient way to do this would be to replicate the vector to the size of the matrix:
julia> a = rand(2,1); A = rand(2,3);
julia> repmat(a,1,3)+A
2x3 Float64 Array:
0.848333 1.66714 1.3262
1.26743 1.77988 1.13859
This is wasteful when dimensions get large, so 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:
julia> broadcast(+, a, A)
2x3 Float64 Array:
0.848333 1.66714 1.3262
1.26743 1.77988 1.13859
julia> b = rand(1,2)
1x2 Float64 Array:
0.629799 0.754948
julia> broadcast(+, a, b)
2x2 Float64 Array:
1.31849 1.44364
1.56107 1.68622
Elementwise operators such as .+ and .* perform broadcasting if necessary. There is also a broadcast! function to specify an explicit destination, and broadcast_getindex and broadcast_setindex! that broadcast the indices before indexing.
The base array type in Julia is the abstract type AbstractArray{T,n}. It is parametrized by the number of dimensions n and the element type T. AbstractVector and AbstractMatrix are aliases for the 1-d and 2-d cases. Operations on AbstractArray objects are defined using higher level operators and functions, in a way that is independent of the underlying storage class. These operations are guaranteed to work correctly as a fallback for any specific array implementation.
The Array{T,n} type is a specific instance of AbstractArray where elements are stored in column-major order. Vector and Matrix are aliases for the 1-d and 2-d cases. Specific operations such as scalar indexing, assignment, and a few other basic storage-specific operations are all that have to be implemented for Array, so that the rest of the array library can be implemented in a generic manner for AbstractArray.
SubArray is a specialization of AbstractArray that performs indexing by reference rather than by copying. A SubArray is created with the sub function, which is called the same way as getindex (with an array and a series of index arguments). The result of sub looks the same as the result of getindex, except the data is left in place. sub stores the input index vectors in a SubArray object, which can later be used to index the original array indirectly.
StridedVector and StridedMatrix are convenient aliases defined to make it possible for Julia to call a wider range of BLAS and LAPACK functions by passing them either Array or SubArray objects, and thus saving inefficiencies from indexing and memory allocation.
The following example computes the QR decomposition of a small section of a larger array, without creating any temporaries, and by calling the appropriate LAPACK function with the right leading dimension size and stride parameters.
julia> a = rand(10,10)
10x10 Float64 Array:
0.763921 0.884854 0.818783 0.519682 … 0.860332 0.882295 0.420202
0.190079 0.235315 0.0669517 0.020172 0.902405 0.0024219 0.24984
0.823817 0.0285394 0.390379 0.202234 0.516727 0.247442 0.308572
0.566851 0.622764 0.0683611 0.372167 0.280587 0.227102 0.145647
0.151173 0.179177 0.0510514 0.615746 0.322073 0.245435 0.976068
0.534307 0.493124 0.796481 0.0314695 … 0.843201 0.53461 0.910584
0.885078 0.891022 0.691548 0.547 0.727538 0.0218296 0.174351
0.123628 0.833214 0.0224507 0.806369 0.80163 0.457005 0.226993
0.362621 0.389317 0.702764 0.385856 0.155392 0.497805 0.430512
0.504046 0.532631 0.477461 0.225632 0.919701 0.0453513 0.505329
julia> b = sub(a, 2:2:8,2:2:4)
4x2 SubArray of 10x10 Float64 Array:
0.235315 0.020172
0.622764 0.372167
0.493124 0.0314695
0.833214 0.806369
julia> (q,r) = qr(b);
julia> q
4x2 Float64 Array:
-0.200268 0.331205
-0.530012 0.107555
-0.41968 0.720129
-0.709119 -0.600124
julia> r
2x2 Float64 Array:
-1.175 -0.786311
0.0 -0.414549
Sparse matrices are matrices that contain enough zeros that storing them in a special data structure leads to savings in space and execution time. Sparse matrices may be used when operations on the sparse representation of a matrix lead to considerable gains in either time or space when compared to performing the same operations on a dense matrix.
In Julia, sparse matrices are stored in the Compressed Sparse Column (CSC) format. Julia sparse matrices have the type SparseMatrixCSC{Tv,Ti}, where Tv is the type of the nonzero values, and Ti is the integer type for storing column pointers and row indices.:
type SparseMatrixCSC{Tv,Ti<:Integer} <: AbstractSparseMatrix{Tv,Ti}
m::Int # Number of rows
n::Int # Number of columns
colptr::Vector{Ti} # Column i is in colptr[i]:(colptr[i+1]-1)
rowval::Vector{Ti} # Row values of nonzeros
nzval::Vector{Tv} # Nonzero values
end
The compressed sparse column storage makes it easy and quick to access the elements in the column of a sparse matrix, whereas accessing the sparse matrix by rows is considerably slower. Operations such as insertion of nonzero values one at a time in the CSC structure tend to be slow. This is because all elements of the sparse matrix that are beyond the point of insertion have to be moved one place over.
All operations on sparse matrices are carefully implemented to exploit the CSC data structure for performance, and to avoid expensive operations.
The simplest way to create sparse matrices are using functions equivalent to the zeros and eye functions that Julia provides for working with dense matrices. To produce sparse matrices instead, you can use the same names with an sp prefix:
julia> spzeros(3,5)
3x5 sparse matrix with 0 nonzeros:
julia> speye(3,5)
3x5 sparse matrix with 3 nonzeros:
[1, 1] = 1.0
[2, 2] = 1.0
[3, 3] = 1.0
The sparse function is often a handy way to construct sparse matrices. It takes as its input a vector I of row indices, a vector J of column indices, and a vector V of nonzero values. sparse(I,J,V) constructs a sparse matrix such that S[I[k], J[k]] = V[k].
julia> I = [1, 4, 3, 5]; J = [4, 7, 18, 9]; V = [1, 2, -5, 3];
julia> S = sparse(I,J,V)
5x18 sparse matrix with 4 nonzeros:
[1 , 4] = 1
[4 , 7] = 2
[5 , 9] = 3
[3 , 18] = -5
The inverse of the sparse function is findn, which retrieves the inputs used to create the sparse matrix.
julia> findn(S)
([1, 4, 5, 3],[4, 7, 9, 18])
julia> findnz(S)
([1, 4, 5, 3],[4, 7, 9, 18],[1, 2, 3, -5])
Another way to create sparse matrices is to convert a dense matrix into a sparse matrix using the sparse function:
julia> sparse(eye(5))
5x5 sparse matrix with 5 nonzeros:
[1, 1] = 1.0
[2, 2] = 1.0
[3, 3] = 1.0
[4, 4] = 1.0
[5, 5] = 1.0
You can go in the other direction using the dense or the full function. The issparse function can be used to query if a matrix is sparse.
julia> issparse(speye(5))
true
Arithmetic operations on sparse matrices also work as they do on dense matrices. Indexing of, assignment into, and concatenation of sparse matrices work in the same way as dense matrices. Indexing operations, especially assignment, are expensive, when carried out one element at a time. In many cases it may be better to convert the sparse matrix into (I,J,V) format using find_nzs, manipulate the non-zeroes or the structure in the dense vectors (I,J,V), and then reconstruct the sparse matrix.
The following table gives a correspondence between built-in methods on sparse matrices and their corresponding methods on dense matrix types. In general, methods that generate sparse matrices differ from their dense counterparts in that the resulting matrix follows the same sparsity pattern as a given sparse matrix S, or that the resulting sparse matrix has density d, i.e. each matrix element has a probability d of being non-zero.
Details can be found in the Sparse Matrices section of the standard library reference.
Sparse | Dense | Description |
spzeros(m,n) | zeros(m,n) | Creates a m-by-n matrix of zeros. (spzeros(m,n) is empty.) |
spones(S) | ones(m,n) | Creates a matrix filled with ones. Unlike the dense version, spones has the same sparsity pattern as S. |
speye(n) | eye(n) | Creates a n-by-n identity matrix. |
dense(S) full(S) | sparse(A) | Interconverts between dense and sparse formats. |
sprand(m,n,d) | rand(m,n) | Creates a m-by-n random matrix (of density d) with iid non-zero elements distributed uniformly on the interval [0, 1]. |
sprandn(m,n,d) | randn(m,n) | Creates a m-by-n random matrix (of density d) with iid non-zero elements distributed according to the standard normal (Gaussian) distribution. |
sprandn(m,n,d,X) | randn(m,n,X) | Creates a m-by-n random matrix (of density d) with iid non-zero elements distributed according to the X distribution. (Requires the Distributions package.) |
sprandbool(m,n,d) | randbool(m,n) | Creates a m-by-n random matrix (of density d) with non-zero Bool elements with probability d (d =0.5 for randbool.) |
Matrix factorizations (a.k.a. matrix decompositions) compute the factorization of a matrix into a product of matrices, and are one of the central concepts in linear algebra.
The following table summarizes the types of matrix factorizations that have been implemented in Julia. Details of their associated methods can be found in the Linear Algebra section of the standard library documentation.
Cholesky | Cholesky factorization |
CholeskyPivoted | Pivoted Cholesky factorization |
LU | LU factorization |
QRPivoted | Pivoted QR factorization |
Hessenberg | Hessenberg decomposition |
Eigen | Spectral decomposition |
SVD | Singular value decomposition |
GeneralizedSVD | Generalized SVD |
Matrices with special symmetries and structures arise often in linear algebra and are frequently associated with various matrix factorizations. Julia features a rich collection of special matrix types, which allow for fast computation with specialized routines that are specially developed for particular matrix types.
The following tables summarize the types of special matrices that have been implemented in Julia, as well as whether hooks to various optimized methods for them in LAPACK are available.
Hermitian | Hermitian matrix |
Triangular | Upper/lower triangular matrix |
Tridiagonal | Tridiagonal matrix |
SymTridiagonal | Symmetric tridiagonal matrix |
Bidiagonal | Upper/lower bidiagonal matrix |
Diagonal | Diagonal matrix |
Matrix type | + | - | * | \ | Other functions with optimized methods |
Hermitian | XY | inv, sqrtm, expm | |||
Triangular | XY | XY | inv, det | ||
SymTridiagonal | X | X | XZ | XY | eigmax/min |
Tridiagonal | X | X | XZ | XY | |
Bidiagonal | X | X | XZ | XY | |
Diagonal | X | X | XY | XY | inv, det, logdet, / |
Legend:
X | An optimized method for matrix-matrix operations is available |
Y | An optimized method for matrix-vector operations is available |
Z | An optimized method for matrix-scalar operations is available |
Matrix type | LAPACK | eig | eigvals | eigvecs | svd | svdvals |
---|---|---|---|---|---|---|
Hermitian | HE | ABC | ||||
Triangular | TR | |||||
SymTridiagonal | ST | A | ABC | AD | ||
Tridiagonal | GT | |||||
Bidiagonal | BD | A | A | |||
Diagonal | DI | A |
Legend:
A | An optimized method to find all the characteristic values and/or vectors is available | e.g. eigvals(M) |
B | An optimized method to find the il^{th} through the ih^{th} characteristic values are available | eigvals(M, il, ih) |
C | An optimized method to find the characteristic values in the interval [vl, vh] is available | eigvals(M, vl, vh) |
D | An optimized method to find the characteristic vectors corresponding to the characteristic values x=[x1, x2,...] is available | eigvecs(M, x) |
Julia provides a rich interface to deal with streaming I/O objects such as Terminals, Pipes and Tcp Sockets. This interface, though asynchronous at the system level, is presented in a synchronous manner to the programmer and it is usually unnecessary to think about the underlying asynchronous operation. This is achieved by making heavy use of Julia cooperative threading (coroutine) functionality.
All Julia streams expose at least a read and a write method, taking the stream as their first argument, e.g.:
julia> write(STDOUT,"Hello World")
Hello World
julia> read(STDIN,Char)
'\n'
Note that I pressed enter again so that Julia would read the newline. Now, as you can see from this example, the write method takes the data to write as its second argument, while the read method takes the type of the data to be read as the second argument. For example, to read a simply byte array, we could do:
julia> x = zeros(Uint8,4)
4-element Uint8 Array:
0x00
0x00
0x00
0x00
julia> read(STDIN,x)
abcd
4-element Uint8 Array:
0x61
0x62
0x63
0x64
However, since this is slightly cumbersome, there are several convenience methods provided. For example, we could have written the above as:
julia> readbytes(STDIN,4)
abcd
4-element Uint8 Array:
0x61
0x62
0x63
0x64
or if we had wanted to read the entire line instead:
julia> readline(STDIN)
abcd
"abcd\n"
Note that depending on your terminal settings, your TTY may be line buffered and might thus require an additional enter before the data is sent to julia.
Note that the write method mentioned above operates on binary streams. In particular, values do not get converted to any canoncical text representation but are written out as is:
julia> write(STDOUT,0x61)
a
For Text I/O, use the print or show methods, depending on your needs (see the standard library reference for a detailed discussion of the difference between the two):
julia> print(STDOUT,0x61)
97
Let’s jump right in with a simple example involving Tcp Sockets. To do, let’s first create a simple server:
julia> @async begin
server = listen(2000)
while true
sock = accept(server)
println("Hello World\n")
end
end
Task
julia>
Those familiar with the Unix socket API, the method names will feel familiar, though their usage is somewhat simpler than the raw Unix socket API. The first call to listen will create a server waiting for incoming connections on the specified port (2000) in this case. The same function may also be used to create various other kinds of servers:
julia> listen(2000) # Listens on localhost:2000 (IPv4)
TcpServer(active)
julia> listen(ip"127.0.0.1",2000) # Equivalent to the first
TcpServer(active)
julia> listen(ip"::1",2000) # Listens on localhost:2000 (IPv6)
TcpServer(active)
julia> listen(IPv4(0),2001) # Listens on port 2001 on all IPv4 interfaces
TcpServer(active)
julia> listen(IPv6(0),2001) # Listens on port 2001 on all IPv6 interfaces
TcpServer(active)
julia> listen("testsocket") # Listens on a domain socket/named pipe
PipeServer(active)
Note that the return type of the last invocation is different. This is because this server does not listen on TCP, but rather on a Named Pipe (Windows terminology) - also called a Domain Socket (UNIX Terminology). The difference is subtle but, has to do with the accept and connect methods. The accept method retrieves a connection to the client that is connecting on the server we just created, while the connect function connects to a server using the specified method. The connect function takes the same arguments as the listen, so, assuming the environment (i.e. host, cwd, etc.) is the same you should be able to pass the same arguments to connect as you did to listen to establish the connection. So let’s try that out (after having created the server above):
julia> connect(2000)
TcpSocket(open, 0 bytes waiting)
julia> Hello World
As expected we saw “Hello World” printed. So, let’s actually analyze what happened behind the scenes. When we called connect, we connect to the server we had just created. Meanwhile, the accept function returns a server-side connection to the newly created socket and prints “Hello World” to indicate that the connection was successful.
A great strength of Julia is that since the API is exposed synchronously even though the I/O is actually happening asynchronously, we didn’t have to worry callbacks or even making sure that the server gets to run. When we called connect the current task waited for the connection to be established and only continued executing after that was done. In this pause, the server task resumed execution (because a connection request was now available), accepted the connection, printed the message and waited for the next client. Reading and writing works in the same way. To see this, consider the following simple echo server:
julia> @async begin
server = listen(2001)
while true
sock = accept(server)
@async while true
write(sock,readline(sock))
end
end
end
Task
julia> clientside=connect(2001)
TcpSocket(open, 0 bytes waiting)
julia> @async while true
write(STDOUT,readline(clientside))
end
julia> println(clientside,"Hello World from the Echo Server")
julia> Hello World from the Echo Server
One of the connect methods that does not follow the listen methods is connect(host::ASCIIString,port), which will attempt to connect to the host given by the host parameter on the port given by the port parameter. It allows you to do things like:
julia> connect("google.com",80)
TcpSocket(open, 0 bytes waiting)
At the base of this functionality is the getaddrinfo function which will do the appropriate address resolution:
julia> Base.getaddrinfo("google.com")
IPv4(74.125.226.225)
Most modern computers possess more than one CPU, and several computers can be combined together in a cluster. Harnessing the power of these multiple CPUs allows many computations to be completed more quickly. There are two major factors that influence performance: the speed of the CPUs themselves, and the speed of their access to memory. In a cluster, it’s fairly obvious that a given CPU will have fastest access to the RAM within the same computer (node). Perhaps more surprisingly, similar issues are very relevant on a typical multicore laptop, due to differences in the speed of main memory and the cache. Consequently, a good multiprocessing environment should allow control over the “ownership” of a chunk of memory by a particular CPU. Julia provides a multiprocessing environment based on message passing to allow programs to run on multiple processes in separate memory domains at once.
Julia’s implementation of message passing is different from other environments such as MPI [1]. Communication in Julia is generally “one-sided”, meaning that the programmer needs to explicitly manage only one process in a two-process operation. Furthermore, these operations typically do not look like “message send” and “message receive” but rather resemble higher-level operations like calls to user functions.
Parallel programming in Julia is built on two primitives: remote references and remote calls. A remote reference is an object that can be used from any process to refer to an object stored on a particular process. A remote call is a request by one process to call a certain function on certain arguments on another (possibly the same) process. A remote call returns a remote reference to its result. Remote calls return immediately; the process that made the call proceeds to its next operation while the remote call happens somewhere else. You can wait for a remote call to finish by calling wait on its remote reference, and you can obtain the full value of the result using fetch. You can store a value to a remote reference using put.
Let’s try this out. Starting with julia -p n provides n worker processes on the local machine. Generally it makes sense for n to equal the number of CPU cores on the machine.
$ ./julia -p 2
julia> r = remotecall(2, rand, 2, 2)
RemoteRef(2,1,5)
julia> fetch(r)
2x2 Float64 Array:
0.60401 0.501111
0.174572 0.157411
julia> s = @spawnat 2 1+fetch(r)
RemoteRef(2,1,7)
julia> fetch(s)
2x2 Float64 Array:
1.60401 1.50111
1.17457 1.15741
The first argument to remotecall is the index of the process that will do the work. Most parallel programming in Julia does not reference specific processes or the number of processes available, but remotecall is considered a low-level interface providing finer control. The second argument to remotecall is the function to call, and the remaining arguments will be passed to this function. As you can see, in the first line we asked process 2 to construct a 2-by-2 random matrix, and in the second line we asked it to add 1 to it. The result of both calculations is available in the two remote references, r and s. The @spawnat macro evaluates the expression in the second argument on the process specified by the first argument.
Occasionally you might want a remotely-computed value immediately. This typically happens when you read from a remote object to obtain data needed by the next local operation. The function remotecall_fetch exists for this purpose. It is equivalent to fetch(remotecall(...)) but is more efficient.
julia> remotecall_fetch(2, getindex, r, 1, 1)
0.10824216411304866
Remember that getindex(r,1,1) is equivalent to r[1,1], so this call fetches the first element of the remote reference r.
The syntax of remotecall is not especially convenient. The macro @spawn makes things easier. It operates on an expression rather than a function, and picks where to do the operation for you:
julia> r = @spawn rand(2,2)
RemoteRef(1,1,0)
julia> s = @spawn 1+fetch(r)
RemoteRef(1,1,1)
julia> fetch(s)
1.10824216411304866 1.13798233877923116
1.12376292706355074 1.18750497916607167
Note that we used 1+fetch(r) instead of 1+r. This is because we do not know where the code will run, so in general a fetch might be required to move r to the process doing the addition. In this case, @spawn is smart enough to perform the computation on the process that owns r, so the fetch will be a no-op.
(It is worth noting that @spawn is not built-in but defined in Julia as a macro. It is possible to define your own such constructs.)
One important point is that your code must be available on any process that runs it. For example, type the following into the julia prompt:
julia> function rand2(dims...)
return 2*rand(dims...)
end
julia> rand2(2,2)
2x2 Float64 Array:
0.153756 0.368514
1.15119 0.918912
julia> @spawn rand2(2,2)
RemoteRef(1,1,1)
julia> @spawn rand2(2,2)
RemoteRef(2,1,2)
julia> exception on 2: in anonymous: rand2 not defined
Processor 1 knew about the function rand2, but process 2 did not. To make your code available to all processes, the require function will automatically load a source file on all currently available processes:
julia> require("myfile")
In a cluster, the contents of the file (and any files loaded recursively) will be sent over the network. It is also useful to execute a statement on all processes. This can be done with the @everywhere macro:
julia> @everywhere id = myid()
julia> remotecall_fetch(2, ()->id)
2
@everywhere include("defs.jl")
A file can also be preloaded on multiple processes at startup, and a driver script can be used to drive the computation:
julia -p <n> -L file1.jl -L file2.jl driver.jl
Each process has an associated identifier. The process providing the interactive julia prompt always has an id equal to 1, as would the julia process running the driver script in the example above. The processors used by default for parallel operations are referred to as workers. When there is only one process, process 1 is considered a worker. Otherwise, workers are considered to be all processes other than process 1.
The base Julia installation has in-built support for two types of clusters:
- A local cluster specified with the -p option as shown above.
- And a cluster spanning machines using the --machinefile option. This uses ssh to start the worker processes on the specified machines.
Functions addprocs, rmprocs, workers and others, are available as a programmatic means of adding, removing and querying the processes in a cluster.
Other types of clusters can be supported by writing your own custom ClusterManager. See section on ClusterManagers.
Sending messages and moving data constitute most of the overhead in a parallel program. Reducing the number of messages and the amount of data sent is critical to achieving performance and scalability. To this end, it is important to understand the data movement performed by Julia’s various parallel programming constructs.
fetch can be considered an explicit data movement operation, since it directly asks that an object be moved to the local machine. @spawn (and a few related constructs) also moves data, but this is not as obvious, hence it can be called an implicit data movement operation. Consider these two approaches to constructing and squaring a random matrix:
# method 1
A = rand(1000,1000)
Bref = @spawn A^2
...
fetch(Bref)
# method 2
Bref = @spawn rand(1000,1000)^2
...
fetch(Bref)
The difference seems trivial, but in fact is quite significant due to the behavior of @spawn. In the first method, a random matrix is constructed locally, then sent to another process where it is squared. In the second method, a random matrix is both constructed and squared on another process. Therefore the second method sends much less data than the first.
In this toy example, the two methods are easy to distinguish and choose from. However, in a real program designing data movement might require more thought and very likely some measurement. For example, if the first process needs matrix A then the first method might be better. Or, if computing A is expensive and only the current process has it, then moving it to another process might be unavoidable. Or, if the current process has very little to do between the @spawn and fetch(Bref) then it might be better to eliminate the parallelism altogether. Or imagine rand(1000,1000) is replaced with a more expensive operation. Then it might make sense to add another @spawn statement just for this step.
Fortunately, many useful parallel computations do not require data movement. A common example is a Monte Carlo simulation, where multiple processes can handle independent simulation trials simultaneously. We can use @spawn to flip coins on two processes. First, write the following function in count_heads.jl:
function count_heads(n)
c::Int = 0
for i=1:n
c += randbool()
end
c
end
The function count_heads simply adds together n random bits. Here is how we can perform some trials on two machines, and add together the results:
require("count_heads")
a = @spawn count_heads(100000000)
b = @spawn count_heads(100000000)
fetch(a)+fetch(b)
This example, as simple as it is, demonstrates a powerful and often-used parallel programming pattern. Many iterations run independently over several processes, and then their results are combined using some function. The combination process is called a reduction, since it is generally tensor-rank-reducing: a vector of numbers is reduced to a single number, or a matrix is reduced to a single row or column, etc. In code, this typically looks like the pattern x = f(x,v[i]), where x is the accumulator, f is the reduction function, and the v[i] are the elements being reduced. It is desirable for f to be associative, so that it does not matter what order the operations are performed in.
Notice that our use of this pattern with count_heads can be generalized. We used two explicit @spawn statements, which limits the parallelism to two processes. To run on any number of processes, we can use a parallel for loop, which can be written in Julia like this:
nheads = @parallel (+) for i=1:200000000
int(randbool())
end
This construct implements the pattern of assigning iterations to multiple processes, and combining them with a specified reduction (in this case (+)). The result of each iteration is taken as the value of the last expression inside the loop. The whole parallel loop expression itself evaluates to the final answer.
Note that although parallel for loops look like serial for loops, their behavior is dramatically different. In particular, the iterations do not happen in a specified order, and writes to variables or arrays will not be globally visible since iterations run on different processes. Any variables used inside the parallel loop will be copied and broadcast to each process.
For example, the following code will not work as intended:
a = zeros(100000)
@parallel for i=1:100000
a[i] = i
end
Notice that the reduction operator can be omitted if it is not needed. However, this code will not initialize all of a, since each process will have a separate copy if it. Parallel for loops like these must be avoided. Fortunately, distributed arrays can be used to get around this limitation, as we will see in the next section.
Using “outside” variables in parallel loops is perfectly reasonable if the variables are read-only:
a = randn(1000)
@parallel (+) for i=1:100000
f(a[randi(end)])
end
Here each iteration applies f to a randomly-chosen sample from a vector a shared by all processes.
In some cases no reduction operator is needed, and we merely wish to apply a function to all integers in some range (or, more generally, to all elements in some collection). This is another useful operation called parallel map, implemented in Julia as the pmap function. For example, we could compute the singular values of several large random matrices in parallel as follows:
M = {rand(1000,1000) for i=1:10}
pmap(svd, M)
Julia’s pmap is designed for the case where each function call does a large amount of work. In contrast, @parallel for can handle situations where each iteration is tiny, perhaps merely summing two numbers. Only worker processes are used by both pmap and @parallel for for the parallel computation. In case of @parallel for, the final reduction is done on the calling process.
Julia’s parallel programming platform uses Tasks (aka Coroutines) to switch among multiple computations. Whenever code performs a communication operation like fetch or wait, the current task is suspended and a scheduler picks another task to run. A task is restarted when the event it is waiting for completes.
For many problems, it is not necessary to think about tasks directly. However, they can be used to wait for multiple events at the same time, which provides for dynamic scheduling. In dynamic scheduling, a program decides what to compute or where to compute it based on when other jobs finish. This is needed for unpredictable or unbalanced workloads, where we want to assign more work to processes only when they finish their current tasks.
As an example, consider computing the singular values of matrices of different sizes:
M = {rand(800,800), rand(600,600), rand(800,800), rand(600,600)}
pmap(svd, M)
If one process handles both 800x800 matrices and another handles both 600x600 matrices, we will not get as much scalability as we could. The solution is to make a local task to “feed” work to each process when it completes its current task. This can be seen in the implementation of pmap:
function pmap(f, lst)
np = nprocs() # determine the number of processes available
n = length(lst)
results = cell(n)
i = 1
# function to produce the next work item from the queue.
# in this case it's just an index.
nextidx() = (idx=i; i+=1; idx)
@sync begin
for p=1:np
if p != myid() || np == 1
@async begin
while true
idx = nextidx()
if idx > n
break
end
results[idx] = remotecall_fetch(p, f, lst[idx])
end
end
end
end
end
results
end
@async is similar to @spawn, but only runs tasks on the local process. We use it to create a “feeder” task for each process. Each task picks the next index that needs to be computed, then waits for its process to finish, then repeats until we run out of indexes. Note that the feeder tasks do not begin to execute until the main task reaches the end of the @sync block, at which point it surrenders control and waits for all the local tasks to complete before returning from the function. The feeder tasks are able to share state via nextidx() because they all run on the same process. No locking is required, since the threads are scheduled cooperatively and not preemptively. This means context switches only occur at well-defined points: in this case, when remotecall_fetch is called.
Large computations are often organized around large arrays of data. In these cases, a particularly natural way to obtain parallelism is to distribute arrays among several processes. This combines the memory resources of multiple machines, allowing use of arrays too large to fit on one machine. Each process operates on the part of the array it owns, providing a ready answer to the question of how a program should be divided among machines.
Julia distributed arrays are implemented by the DArray type. A DArray has an element type and dimensions just like an Array. A DArray can also use arbitrary array-like types to represent the local chunks that store actual data. The data in a DArray is distributed by dividing the index space into some number of blocks in each dimension.
Common kinds of arrays can be constructed with functions beginning with d:
dzeros(100,100,10)
dones(100,100,10)
drand(100,100,10)
drandn(100,100,10)
dfill(x, 100,100,10)
In the last case, each element will be initialized to the specified value x. These functions automatically pick a distribution for you. For more control, you can specify which processors to use, and how the data should be distributed:
dzeros((100,100), workers()[1:4], [1,4])
The second argument specifies that the array should be created on the first four workers. When dividing data among a large number of processes, one often sees diminishing returns in performance. Placing DArrays on a subset of processes allows multiple DArray computations to happen at once, with a higher ratio of work to communication on each process.
The third argument specifies a distribution; the nth element of this array specifies how many pieces dimension n should be divided into. In this example the first dimension will not be divided, and the second dimension will be divided into 4 pieces. Therefore each local chunk will be of size (100,25). Note that the product of the distribution array must equal the number of processors.
distribute(a::Array) converts a local array to a distributed array.
localpart(a::DArray) obtains the locally-stored portion of a DArray.
myindexes(a::DArray) gives a tuple of the index ranges owned by the local process.
convert(Array, a::DArray) brings all the data to the local processor.
Indexing a DArray (square brackets) with ranges of indexes always creates a SubArray, not copying any data.
The primitive DArray constructor has the following somewhat elaborate signature:
DArray(init, dims[, procs, dist])
init is a function that accepts a tuple of index ranges. This function should allocate a local chunk of the distributed array and initialize it for the specified indices. dims is the overall size of the distributed array. procs optionally specifies a vector of processor IDs to use. dist is an integer vector specifying how many chunks the distributed array should be divided into in each dimension.
The last two arguments are optional, and defaults will be used if they are omitted.
As an example, here is how to turn the local array constructor fill into a distributed array constructor:
dfill(v, args...) = DArray(I->fill(v, map(length,I)), args...)
In this case the init function only needs to call fill with the dimensions of the local piece it is creating.
At this time, distributed arrays do not have much functionality. Their major utility is allowing communication to be done via array indexing, which is convenient for many problems. As an example, consider implementing the “life” cellular automaton, where each cell in a grid is updated according to its neighboring cells. To compute a chunk of the result of one iteration, each processor needs the immediate neighbor cells of its local chunk. The following code accomplishes this:
function life_step(d::DArray)
DArray(size(d),procs(d)) do I
top = mod(first(I[1])-2,size(d,1))+1
bot = mod( last(I[1]) ,size(d,1))+1
left = mod(first(I[2])-2,size(d,2))+1
right = mod( last(I[2]) ,size(d,2))+1
old = Array(Bool, length(I[1])+2, length(I[2])+2)
old[1 , 1 ] = d[top , left] # left side
old[2:end-1, 1 ] = d[I[1], left]
old[end , 1 ] = d[bot , left]
old[1 , 2:end-1] = d[top , I[2]]
old[2:end-1, 2:end-1] = d[I[1], I[2]] # middle
old[end , 2:end-1] = d[bot , I[2]]
old[1 , end ] = d[top , right] # right side
old[2:end-1, end ] = d[I[1], right]
old[end , end ] = d[bot , right]
life_rule(old)
end
end
As you can see, we use a series of indexing expressions to fetch data into a local array old. Note that the do block syntax is convenient for passing init functions to the DArray constructor. Next, the serial function life_rule is called to apply the update rules to the data, yielding the needed DArray chunk. Nothing about life_rule is DArray-specific, but we list it here for completeness:
function life_rule(old)
m, n = size(old)
new = similar(old, m-2, n-2)
for j = 2:n-1
for i = 2:m-1
nc = +(old[i-1,j-1], old[i-1,j], old[i-1,j+1],
old[i ,j-1], old[i ,j+1],
old[i+1,j-1], old[i+1,j], old[i+1,j+1])
new[i-1,j-1] = (nc == 3 ? 1 :
nc == 2 ? old[i,j] :
0)
end
end
new
end
Julia worker processes can also be spawned on arbitrary machines, enabling Julia’s natural parallelism to function quite transparently in a cluster environment. The ClusterManager interface provides a way to specify a means to launch and manage worker processes. For example, ssh clusters are also implemented using a ClusterManager:
immutable SSHManager <: ClusterManager
launch::Function
manage::Function
machines::AbstractVector
SSHManager(; machines=[]) = new(launch_ssh_workers, manage_ssh_workers, machines)
end
function launch_ssh_workers(cman::SSHManager, np::Integer, config::Dict)
...
end
function manage_ssh_workers(id::Integer, config::Dict, op::Symbol)
...
end
where launch_ssh_workers is responsible for instantiating new Julia processes and manage_ssh_workers provides a means to manage those processes, e.g. for sending interrupt signals. New processes can then be added at runtime using addprocs:
addprocs(5, cman=LocalManager())
which specifies a number of processes to add and a ClusterManager to use for launching those processes.
Footnotes
[1] | In this context, MPI refers to the MPI-1 standard. Beginning with MPI-2, the MPI standards committee introduced a new set of communication mechanisms, collectively referred to as Remote Memory Access (RMA). The motivation for adding RMA to the MPI standard was to facilitate one-sided communication patterns. For additional information on the latest MPI standard, see http://www.mpi-forum.org/docs. |
Julia borrows backtick notation for commands from the shell, Perl, and Ruby. However, in Julia, writing
julia> `echo hello`
`echo hello`
differs in a several aspects from the behavior in various shells, Perl, or Ruby:
Here’s a simple example of actually running an external program:
julia> run(`echo hello`)
hello
The hello is the output of the echo command, sent to stdout. The run method itself returns nothing, and throws an ErrorException 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:
julia> a=readall(`echo hello`)
"hello\n"
julia> (chomp(a)) == "hello"
true
Suppose you want to do something a bit more complicated and use the name of a file in the variable file as an argument to a command. You can use $ for interpolation much as you would in a string literal (see Strings):
julia> file = "/etc/passwd"
"/etc/passwd"
julia> `sort $file`
`sort /etc/passwd`
A common pitfall when running external programs via a shell is that if a file name contains characters that are special to the shell, they may cause undesirable behavior. Suppose, for example, rather than /etc/passwd, we wanted to sort the contents of the file /Volumes/External HD/data.csv. Let’s try it:
julia> file = "/Volumes/External HD/data.csv"
"/Volumes/External HD/data.csv"
julia> `sort $file`
`sort '/Volumes/External HD/data.csv'`
How did the file name get quoted? Julia knows that file is meant to be interpolated as a single argument, so it quotes the word for you. Actually, that is not quite accurate: the value of file is never interpreted by a shell, so there’s no need for actual quoting; the quotes are inserted only for presentation to the user. This will even work if you interpolate a value as part of a shell word:
julia> path = "/Volumes/External HD"
"/Volumes/External HD"
julia> name = "data"
"data"
julia> ext = "csv"
"csv"
julia> `sort $path/$name.$ext`
`sort '/Volumes/External HD/data.csv'`
As you can see, the space in the path variable is appropriately escaped. But what if you want to interpolate multiple words? In that case, just use an array (or any other iterable container):
julia> files = ["/etc/passwd","/Volumes/External HD/data.csv"]
2-element ASCIIString Array:
"/etc/passwd"
"/Volumes/External HD/data.csv"
julia> `grep foo $files`
`grep foo /etc/passwd '/Volumes/External HD/data.csv'`
If you interpolate an array as part of a shell word, Julia emulates the shell’s {a,b,c} argument generation:
julia> names = ["foo","bar","baz"]
3-element ASCIIString Array:
"foo"
"bar"
"baz"
julia> `grep xylophone $names.txt`
`grep xylophone foo.txt bar.txt baz.txt`
Moreover, if you interpolate multiple arrays into the same word, the shell’s Cartesian product generation behavior is emulated:
julia> names = ["foo","bar","baz"]
3-element ASCIIString Array:
"foo"
"bar"
"baz"
julia> exts = ["aux","log"]
2-element ASCIIString Array:
"aux"
"log"
julia> `rm -f $names.$exts`
`rm -f foo.aux foo.log bar.aux bar.log baz.aux baz.log`
Since you can interpolate literal arrays, you can use this generative functionality without needing to create temporary array objects first:
julia> `rm -rf $["foo","bar","baz","qux"].$["aux","log","pdf"]`
`rm -rf foo.aux foo.log foo.pdf bar.aux bar.log bar.pdf baz.aux baz.log baz.pdf qux.aux qux.log qux.pdf`
Inevitably, one wants to write commands that aren’t quite so simple, and it becomes necessary to use quotes. Here’s a simple example of a perl one-liner at a shell prompt:
sh$ perl -le '$|=1; for (0..3) { print }'
0
1
2
3
The Perl expression needs to be in single quotes for two reasons: so that spaces don’t break the expression into multiple shell words, and so that uses of Perl variables like $| (yes, that’s the name of a variable in Perl), don’t cause interpolation. In other instances, you may want to use double quotes so that interpolation does occur:
sh$ first="A"
sh$ second="B"
sh$ perl -le '$|=1; print for @ARGV' "1: $first" "2: $second"
1: A
2: B
In general, the Julia backtick syntax is carefully designed so that you can just cut-and-paste shell commands as-is into backticks and they will work: the escaping, quoting, and interpolation behaviors are the same as the shell’s. The only difference is that the interpolation is integrated and aware of Julia’s notion of what is a single string value, and what is a container for multiple values. Let’s try the above two examples in Julia:
julia> `perl -le '$|=1; for (0..3) { print }'`
`perl -le '$|=1; for (0..3) { print }'`
julia> run(ans)
0
1
2
3
julia> first = "A"; second = "B";
julia> `perl -le 'print for @ARGV' "1: $first" "2: $second"`
`perl -le 'print for @ARGV' '1: A' '2: B'`
julia> run(ans)
1: A
2: B
The results are identical, and Julia’s interpolation behavior mimics the shell’s with some improvements due to the fact that Julia supports first-class iterable objects while most shells use strings split on spaces for this, which introduces ambiguities. When trying to port shell commands to Julia, try cut and pasting first. Since Julia shows commands to you before running them, you can easily and safely just examine its interpretation without doing any damage.
Shell metacharacters, such as |, &, and >, are not special inside of Julia’s backticks: unlike in the shell, inside of Julia’s backticks, a pipe is always just a pipe:
julia> run(`echo hello | sort`)
hello | sort
This expression invokes the echo command with three words as arguments: “hello”, “|”, and “sort”. The result is that a single line is printed: “hello | sort”. Inside of backticks, a “|” is just a literal pipe character. How, then, does one construct a pipeline? Instead of using “|” inside of backticks, one uses Julia’s |> operator between Cmd objects:
julia> run(`echo hello` |> `sort`)
hello
This pipes the output of the echo command to the sort command. Of course, this isn’t terribly interesting since there’s only one line to sort, but we can certainly do much more interesting things:
julia> run(`cut -d: -f3 /etc/passwd` |> `sort -n` |> `tail -n5`)
210
211
212
213
214
This prints the highest five user IDs on a UNIX system. The cut, sort and tail commands are all spawned as immediate children of the current julia process, with no intervening shell process. Julia itself does the work to setup pipes and connect file descriptors that is normally done by the shell. Since Julia does this itself, it retains better control and can do some things that shells cannot.
Julia can run multiple commands in parallel:
julia> run(`echo hello` & `echo world`)
world
hello
The order of the output here is non-deterministic because the two echo processes are started nearly simultaneously, and race to make the first write to the stdout descriptor they share with each other and the julia parent process. Julia lets you pipe the output from both of these processes to another program:
julia> run(`echo world` & `echo hello` |> `sort`)
hello
world
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 the sort command.
The combination of a high-level programming language, a first-class command abstraction, and automatic setup of pipes between processes is a powerful one. To give some sense of the complex pipelines that can be created easily, here are some more sophisticated examples, with apologies for the excessive use of Perl one-liners:
julia> prefixer(prefix, sleep) = `perl -nle '$|=1; print "'$prefix' ", $_; sleep '$sleep';'`
julia> run(`perl -le '$|=1; for(0..9){ print; sleep 1 }'` |> prefixer("A",2) & prefixer("B",2))
A 0
B 1
A 2
B 3
A 4
B 5
A 6
B 7
A 8
B 9
This is a classic example of a single producer feeding two concurrent consumers: one perl process generates lines with the numbers 0 through 9 on them, while two parallel processes consume that output, one prefixing lines with the letter “A”, the other with the letter “B”. Which consumer gets the first line is non-deterministic, but once that race has been won, the lines are consumed alternately by one process and then the other. (Setting $|=1 in Perl causes each print statement to flush the stdout handle, which is necessary for this example to work. Otherwise all the output is buffered and printed to the pipe at once, to be read by just one consumer process.)
Here is an even more complex multi-stage producer-consumer example:
julia> run(`perl -le '$|=1; for(0..9){ print; sleep 1 }'` |>
prefixer("X",3) & prefixer("Y",3) & prefixer("Z",3) |>
prefixer("A",2) & prefixer("B",2))
B Y 0
A Z 1
B X 2
A Y 3
B Z 4
A X 5
B Y 6
A Z 7
B X 8
A Y 9
This example is similar to the previous one, except there are two stages of consumers, and the stages have different latency so they use a different number of parallel workers, to maintain saturated throughput.
We strongly encourage you to try all these examples to see how they work.
Though most code can be written in Julia, there are many high-quality, mature libraries for numerical computing already written in C and Fortran. To allow easy use of this existing code, Julia makes it simple and efficient to call C and Fortran functions. Julia has a “no boilerplate” philosophy: functions can be called directly from Julia without any “glue” code, code generation, or compilation — even from the interactive prompt. This is accomplished just by making an appropriate call with call syntax, which looks like an ordinary function call.
The code to be called must be available as a shared library. Most C and Fortran libraries ship compiled as shared libraries already, but if you are compiling the code yourself using GCC (or Clang), you will need to use the -shared and -fPIC options. The machine instructions generated by Julia’s JIT are the same as a native C call would be, so the resulting overhead is the same as calling a library function from C code. (Non-library function calls in both C and Julia can be inlined and thus may have even less overhead than calls to shared library functions. When both libraries and executables are generated by LLVM, it is possible to perform whole-program optimizations that can even optimize across this boundary, but Julia does not yet support that. In the future, however, it may do so, yielding even greater performance gains.)
Shared libraries and functions are referenced by a tuple of the form (:function, "library") or ("function", "library") where function is the C-exported function name. library refers to the shared library name: shared libraries available in the (platform-specific) load path will be resolved by name, and if necessary a direct path may be specified.
A function name may be used alone in place of the tuple (just :function or "function"). In this case the name is resolved within the current process. This form can be used to call C library functions, functions in the Julia runtime, or functions in an application linked to Julia.
Finally, you can use ccall to actually generate a call to the library function. Arguments to ccall are as follows:
As a complete but simple example, the following calls the clock function from the standard C library:
julia> t = ccall( (:clock, "libc"), Int32, ())
2292761
julia> t
2292761
julia> typeof(ans)
Int32
clock takes no arguments and returns an Int32. One common gotcha is that a 1-tuple must be written with a trailing comma. For example, to call the getenv function to get a pointer to the value of an environment variable, one makes a call like this:
julia> path = ccall( (:getenv, "libc"), Ptr{Uint8}, (Ptr{Uint8},), "SHELL")
Ptr{Uint8} @0x00007fff5fbffc45
julia> bytestring(path)
"/bin/bash"
Note that the argument type tuple must be written as (Ptr{Uint8},), rather than (Ptr{Uint8}). This is because (Ptr{Uint8}) is just Ptr{Uint8}, rather than a 1-tuple containing Ptr{Uint8}:
julia> (Ptr{Uint8})
Ptr{Uint8}
julia> (Ptr{Uint8},)
(Ptr{Uint8},)
In practice, especially when providing reusable functionality, one generally wraps ccall uses in Julia functions that set up arguments and then check for errors in whatever manner the C or Fortran function indicates them, propagating to the Julia caller as exceptions. This is especially important since C and Fortran APIs are notoriously inconsistent about how they indicate error conditions. For example, the getenv C library function is wrapped in the following Julia function in env.jl:
function getenv(var::String)
val = ccall( (:getenv, "libc"),
Ptr{Uint8}, (Ptr{Uint8},), bytestring(var))
if val == C_NULL
error("getenv: undefined variable: ", var)
end
bytestring(val)
end
The C getenv function indicates an error by returning NULL, but other standard C functions indicate errors in various different ways, including by returning -1, 0, 1 and other special values. This wrapper throws an exception clearly indicating the problem if the caller tries to get a non-existent environment variable:
julia> getenv("SHELL")
"/bin/bash"
julia> getenv("FOOBAR")
getenv: undefined variable: FOOBAR
Here is a slightly more complex example that discovers the local machine’s hostname:
function gethostname()
hostname = Array(Uint8, 128)
ccall( (:gethostname, "libc"), Int32,
(Ptr{Uint8}, Uint),
hostname, length(hostname))
return bytestring(convert(Ptr{Uint8}, hostname))
end
This example first allocates an array of bytes, then calls the C library function gethostname to fill the array in with the hostname, takes a pointer to the hostname buffer, and converts the pointer to a Julia string, assuming that it is a NUL-terminated C string. It is common for C libraries to use this pattern of requiring the caller to allocate memory to be passed to the callee and filled in. Allocation of memory from Julia like this is generally accomplished by creating an uninitialized array and passing a pointer to its data to the C function.
When calling a Fortran function, all inputs must be passed by reference.
A prefix & is used to indicate that a pointer to a scalar argument should be passed instead of the scalar value itself. The following example computes a dot product using a BLAS function.
function compute_dot(DX::Vector, DY::Vector)
assert(length(DX) == length(DY))
n = length(DX)
incx = incy = 1
product = ccall( (:ddot_, "libLAPACK"),
Float64,
(Ptr{Int32}, Ptr{Float64}, Ptr{Int32}, Ptr{Float64}, Ptr{Int32}),
&n, DX, &incx, DY, &incy)
return product
end
The meaning of prefix & is not quite the same as in C. In particular, any changes to the referenced variables will not be visible in Julia. However, it will never cause any harm for called functions to attempt such modifications (that is, writing through the passed pointers). Since this & is not a real address operator, it may be used with any syntax, such as &0 or &f(x).
Note that no C header files are used anywhere in the process. Currently, it is not possible to pass structs and other non-primitive types from Julia to C libraries. However, C functions that generate and use opaque structs types by passing around pointers to them can return such values to Julia as Ptr{Void}, which can then be passed to other C functions as Ptr{Void}. Memory allocation and deallocation of such objects must be handled by calls to the appropriate cleanup routines in the libraries being used, just like in any C program.
Julia automatically inserts calls to the convert function to convert each argument to the specified type. For example, the following call:
ccall( (:foo, "libfoo"), Void, (Int32, Float64),
x, y)
will behave as if the following were written:
ccall( (:foo, "libfoo"), Void, (Int32, Float64),
convert(Int32, x), convert(Float64, y))
When a scalar value is passed with & as an argument of type Ptr{T}, the value will first be converted to type T.
When an Array is passed to C as a Ptr argument, it is “converted” simply by taking the address of the first element. This is done in order to avoid copying arrays unnecessarily, and to tolerate the slight mismatches in pointer types that are often encountered in C APIs (for example, passing a Float64 array to a function that operates on uninterpreted bytes).
Therefore, if an Array contains data in the wrong format, it will have to be explicitly converted using a call such as int32(a).
On all systems we currently support, basic C/C++ value types may be translated to Julia types as follows. Every C type also has a corresponding Julia type with the same name, prefixed by C. This can help for writing portable code (and remembering that an int in C is not the same as an Int in Julia).
System-independent:
bool (8 bits) | Cbool | Bool |
signed char | Int8 | |
unsigned char | Cuchar | Uint8 |
short | Cshort | Int16 |
unsigned short | Cushort | Uint16 |
int | Cint | Int32 |
unsigned int | Cuint | Uint32 |
long long | Clonglong | Int64 |
unsigned long long | Culonglong | Uint64 |
float | Cfloat | Float32 |
double | Cdouble | Float64 |
ptrdiff_t | Cptrdiff_t | Int |
ssize_t | Cssize_t | Int |
size_t | Csize_t | Uint |
complex float | Ccomplex_float (future addition) | |
complex double | Ccomplex_double (future addition) | |
void | Void | |
void* | Ptr{Void} | |
char* (or char[], e.g. a string) | Ptr{Uint8} | |
char** (or *char[]) | Ptr{Ptr{Uint8}} | |
struct T* (where T represents an appropriately defined bits type) | Ptr{T} (call using &variable_name in the parameter list) | |
struct T (where T represents an appropriately defined bits type) | T (call using &variable_name in the parameter list) | |
jl_value_t* (any Julia Type) | Ptr{Any} |
Note: the bool type is only defined by C++, where it is 8 bits wide. In C, however, int is often used for boolean values. Since int is 32-bits wide (on all supported systems), there is some potential for confusion here.
Julia’s Char type is 32 bits, which is not the same as the wide character type (wchar_t or wint_t) on all platforms.
A C function declared to return void will give nothing in Julia.
System-dependent:
char | Cchar | Int8 (x86, x86_64) Uint8 (powerpc, arm) |
long | Clong | Int (UNIX) Int32 (Windows) |
unsigned long | Culong | Uint (UNIX) Uint32 (Windows) |
wchar_t | Cwchar_t | Int32 (UNIX) Uint16 (Windows) |
For string arguments (char*) the Julia type should be Ptr{Uint8}, not ASCIIString. C functions that take an argument of the type char** can be called by using a Ptr{Ptr{Uint8}} type within Julia. For example, C functions of the form:
int main(int argc, char **argv);
can be called via the following Julia code:
argv = [ "a.out", "arg1", "arg2" ]
ccall(:main, Int32, (Int32, Ptr{Ptr{Uint8}}), length(argv), argv)
The following methods are described as “unsafe” because they can cause Julia to terminate abruptly or corrupt arbitrary process memory due to a bad pointer or type declaration.
Given a Ptr{T}, the contents of type T can generally be copied from the referenced memory into a Julia object using unsafe_load(ptr, [index]). The index argument is optional (default is 1), and performs 1-based indexing. This function is intentionally similar to the behavior of getindex() and setindex!() (e.g. [] access syntax).
The return value will be a new object initialized to contain a copy of the contents of the referenced memory. The referenced memory can safely be freed or released.
If T is Any, then the memory is assumed to contain a reference to a Julia object (a jl_value_t*), the result will be a reference to this object, and the object will not be copied. You must be careful in this case to ensure that the object was always visible to the garbage collector (pointers do not count, but the new reference does) to ensure the memory is not prematurely freed. Note that if the object was not originally allocated by Julia, the new object will never be finalized by Julia’s garbage collector. If the Ptr itself is actually a jl_value_t*, it can be converted back to a Julia object reference by unsafe_pointer_to_objref(ptr). (Julia values v can be converted to jl_value_t* pointers, as Ptr{Void}, by calling pointer_from_objref(v).)
The reverse operation (writing data to a Ptr{T}), can be performed using unsafe_store!(ptr, value, [index]). Currently, this is only supported for bitstypes or other pointer-free (isbits) immutable types.
Any operation that throws an error is probably currently unimplemented and should be posted as a bug so that it can be resolved.
If the pointer of interest is a plain-data array (bitstype or immutable), the function pointer_to_array(ptr,dims,[own]) may be more useful. The final parameter should be true if Julia should “take ownership” of the underlying buffer and call free(ptr) when the returned Array object is finalized. If the own parameter is omitted or false, the caller must ensure the buffer remains in existence until all access is complete.
When passing data to a ccall, it is best to avoid using the pointer() function. Instead define a convert method and pass the variables directly to the ccall. ccall automatically arranges that all of its arguments will be preserved from garbage collection until the call returns. If a C API will store a reference to memory allocated by Julia, after the ccall returns, you must arrange that the object remains visible to the garbage collector. The suggested way to handle this is to make a global variable of type Array{Any,1} to hold these values, until C interface notifies you that it is finished with them.
Whenever you have created a pointer to Julia data, you must ensure the original data exists until you are done with using the pointer. Many methods in Julia such as unsafe_load() and bytestring() make copies of data instead of taking ownership of the buffer, so that it is safe to free (or alter) the original data without affecting Julia. A notable exception is pointer_to_array() which, for performance reasons, shares (or can be told to take ownership of) the underlying buffer.
The garbage collector does not guarantee any order of finalization. That is, if a contained a reference to b and both a and b are due for garbage collection, there is no guarantee that b would be finalized after a. If proper finalization of a depends on b being valid, it must be handled in other ways.
A (name, library) function specification must be a constant expression. However, it is possible to use computed values as function names by staging through eval as follows:
@eval ccall(($(string("a","b")),"lib"), ...
This expression constructs a name using string, then substitutes this name into a new ccall expression, which is then evaluated. Keep in mind that eval only operates at the top level, so within this expression local variables will not be available (unless their values are substituted with $). For this reason, eval is typically only used to form top-level definitions, for example when wrapping libraries that contain many similar functions.
The first argument to ccall can also be an expression evaluated at run time. In this case, the expression must evaluate to a Ptr, which will be used as the address of the native function to call. This behavior occurs when the first ccall argument contains references to non-constants, such as local variables or function arguments.
The second argument to ccall can optionally be a calling convention specifier (immediately preceding return type). Without any specifier, the platform-default C calling convention is used. Other supported conventions are: stdcall, cdecl, fastcall, and thiscall. For example (from base/libc.jl):
hn = Array(Uint8, 256)
err=ccall(:gethostname, stdcall, Int32, (Ptr{Uint8}, Uint32), hn, length(hn))
For more information, please see the LLVM Language Reference.
Global variables exported by native libraries can be accessed by name using the cglobal function. The arguments to cglobal are a symbol specification identical to that used by ccall, and a type describing the value stored in the variable:
julia> cglobal((:errno,:libc), Int32)
Ptr{Int32} @0x00007f418d0816b8
The result is a pointer giving the address of the value. The value can be manipulated through this pointer using unsafe_load and unsafe_store.
It is possible to pass Julia functions to native functions that accept function pointer arguments. A classic example is the standard C library qsort function, declared as:
void qsort(void *base, size_t nmemb, size_t size,
int(*compare)(const void *a, const void *b));
The base argument is a pointer to an array of length nmemb, with elements of size bytes each. compare is a callback function which takes pointers to two elements a and b and returns an integer less/greater than zero if a should appear before/after b (or zero if any order is permitted). Now, suppose that we have a 1d array A of values in Julia that we want to sort using the qsort function (rather than Julia’s built-in sort function). Before we worry about calling qsort and passing arguments, we need to write a comparison function that works for some arbitrary type T:
function mycompare{T}(a_::Ptr{T}, b_::Ptr{T})
a = unsafe_load(a_)
b = unsafe_load(b_)
return convert(Cint, a < b ? -1 : a > b ? +1 : 0)
end
Notice that we have to be careful about the return type: qsort expects a function returning a C int, so we must be sure to return Cint via a call to convert.
In order to pass this function to C, we obtain its address using the function cfunction:
const mycompare_c = cfunction(mycompare, Cint, (Ptr{Cdouble}, Ptr{Cdouble}))
cfunction accepts three arguments: the Julia function (mycompare), the return type (Cint), and a tuple of the argument types, in this case to sort an array of Cdouble (Float64) elements.
The final call to qsort looks like this:
A = [1.3, -2.7, 4.4, 3.1]
ccall(:qsort, Void, (Ptr{Cdouble}, Csize_t, Csize_t, Ptr{Void}),
A, length(A), sizeof(eltype(A)), mycompare_c)
After this executes, A is changed to the sorted array [ -2.7, 1.3, 3.1, 4.4]. Note that Julia knows how to convert an array into a Ptr{Cdouble}, how to compute the size of a type in bytes (identical to C’s sizeof operator), and so on. For fun, try inserting a println("mycompare($a,$b)") line into mycompare, which will allow you to see the comparisons that qsort is performing (and to verify that it is really calling the Julia function that you passed to it).
When dealing with platform libraries, it is often necessary to provide special cases for various platforms. The variable OS_NAME can be used to write these special cases. Additionally, there are several macros intended to make this easier: @windows, @unix, @linux, and @osx. Note that linux and osx are mutually exclusive subsets of unix. Their usage takes the form of a ternary conditional operator, as demonstrated in the following examples.
Simple blocks:
ccall( (@windows? :_fopen : :fopen), ...)
Complex blocks:
@linux? (
begin
some_complicated_thing(a)
end
: begin
some_different_thing(a)
end
)
Chaining (parentheses optional, but recommended for readability):
@windows? :a : (@osx? :b : :c)
Julia has a built-in package manager for installing add-on functionality written in Julia. It can also install external libraries using your operating system’s standard system for doing so, or by compiling from source. The list of registered Julia packages can be found at Available 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. Initially, you’ll have no packages installed:
julia> Pkg.status()
INFO: Initializing package repository /Users/stefan/.julia
INFO: Cloning METADATA from git://github.com/JuliaLang/METADATA.jl
No packages installed.
Your package directory is automatically initialized the first time you run a Pkg command that expects it to exist – which includes Pkg.status(). Here’s an example non-trivial set of required and additional packages:
julia> Pkg.status()
Required packages:
- Distributions 0.2.8
- UTF16 0.2.0
Additional packages:
- NumericExtensions 0.2.17
- Stats 0.2.6
These packages are all on registered versions, managed by Pkg. Packages can be in more complicated states, indicated by annotations to the right of the installed package version; we will explain these states and annotations as we encounter them. For programmatic usage, Pkg.installed() returns a dictionary, mapping installed package names to the version of that package which is installed:
julia> Pkg.installed()
["Distributions"=>v"0.2.8","Stats"=>v"0.2.6","UTF16"=>v"0.2.0","NumericExtensions"=>v"0.2.17"]
Julia’s package manager is a little unusual in that it is declarative rather than imperative. This means that you tell it what you want and it figures out what versions to install (or remove) to satisfy those requirements optimally – and minimally. So rather than installing a package, you just add it to the list of requirements and then “resolve” what needs to be installed. In particular, this means that 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/REQUIRE. You can edit this file by hand and then call Pkg.resolve() to install, upgrade or remove packages to optimally satisfy the requirements, or you can do Pkg.edit(), which will open REQUIRE in your editor (configured via the EDITOR or VISUAL environment variables), and then automatically call Pkg.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 and Pkg.rm commands, which add or remove a single requirement to REQUIRE and then call Pkg.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:
julia> Pkg.status()
No packages installed.
julia> Pkg.add("Distributions")
INFO: Cloning cache of Distributions from git://github.com/JuliaStats/Distributions.jl.git
INFO: Cloning cache of NumericExtensions from git://github.com/lindahua/NumericExtensions.jl.git
INFO: Cloning cache of Stats from git://github.com/JuliaStats/Stats.jl.git
INFO: Installing Distributions v0.2.7
INFO: Installing NumericExtensions v0.2.17
INFO: Installing Stats v0.2.6
INFO: REQUIRE updated.
julia> Pkg.status()
Required packages:
- Distributions 0.2.7
Additional packages:
- NumericExtensions 0.2.17
- Stats 0.2.6
What this is doing is first adding Distributions to your ~/.julia/REQUIRE file:
$ cat ~/.julia/REQUIRE
Distributions
It then runs Pkg.resolve() using these new requirements, which leads to the conclusion that the Distributions package should be installed since it is required but not installed. As stated before, you can accomplish the same thing by editing your ~/.julia/REQUIRE file by hand and then running Pkg.resolve() yourself:
$ echo UTF16 >> ~/.julia/REQUIRE
julia> Pkg.resolve()
INFO: Cloning cache of UTF16 from git://github.com/nolta/UTF16.jl.git
INFO: Installing UTF16 v0.2.0
julia> Pkg.status()
Required packages:
- Distributions 0.2.7
- UTF16 0.2.0
Additional packages:
- NumericExtensions 0.2.17
- Stats 0.2.6
This is functionally equivalent to calling Pkg.add("UTF16"), except that Pkg.add doesn’t change REQUIRE until after installation has completed, so if there are problems, REQUIRE will be left as it was before calling Pkg.add. The format of the REQUIRE file is described in Requirements; it allows, among other things, requiring specific ranges of versions of packages.
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 the REQUIRE file:
julia> Pkg.rm("Distributions")
INFO: Removing Distributions v0.2.7
INFO: Removing Stats v0.2.6
INFO: Removing NumericExtensions v0.2.17
INFO: REQUIRE updated.
julia> Pkg.status()
Required packages:
- UTF16 0.2.0
julia> Pkg.rm("UTF16")
INFO: Removing UTF16 v0.2.0
INFO: REQUIRE updated.
julia> Pkg.status()
No packages installed.
Once again, this is equivalent to editing the REQUIRE file to remove the line with each package name on it then running Pkg.resolve() to update the set of installed packages to match. While Pkg.add and Pkg.rm are convenient for adding and removing requirements for a single package, when you want to add or remove multiple packages, you can call Pkg.edit() to manually change the contents of REQUIRE and then update your packages accordingly. Pkg.edit() does not roll back the contents of REQUIRE if Pkg.resolve() fails – rather, you have to run Pkg.edit() again to fix the files contents yourself.
Julia packages are simply git repositories, clonable via any of the protocols that git supports, and containing Julia code that follows certain layout conventions. Official Julia packages are registered in the METADATA.jl repository, available at a well-known location [1]. The Pkg.add and Pkg.rm commands in the previous section interact with registered packages, but the package manager can install and work with unregistered packages too. To install an unregistered package, use Pkg.clone(url), where url is a git URL from which the package can be cloned:
julia> Pkg.clone("git://example.com/path/to/Package.jl.git")
INFO: Cloning Package from git://example.com/path/to/Package.jl.git
Cloning into 'Package'...
remote: Counting objects: 22, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 22 (delta 8), reused 22 (delta 8)
Receiving objects: 100% (22/22), 2.64 KiB, done.
Resolving deltas: 100% (8/8), done.
By convention, Julia repository names end with .jl (the additional .git indicates a “bare” git repository), which keeps them from colliding with repositories for other languages, and also makes Julia packages easy to find in search engines. When packages are installed in your .julia directory, however, the extension is redundant so we leave it off.
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. Unregistered packages participate in the same version resolution logic as registered packages, so installed package versions will be adjusted as necessary to satisfy the requirements of both registered and unregistered packages.
[1] | The official set of packages is at https://github.com/JuliaLang/METADATA.jl, but individuals and organizations can easily use a different metadata repository. This allows control which packages are available for automatic installation. One can allow only audited and approved package versions, and make private packages or forks available. |
When package developers publish new registered versions of packages that you’re using, you will, of course, want the new shiny versions. To get the latest and greatest versions of all your packages, just do Pkg.update():
julia> Pkg.update()
INFO: Updating METADATA...
INFO: Computing changes...
INFO: Upgrading Distributions: v0.2.8 => v0.2.10
INFO: Upgrading Stats: v0.2.7 => v0.2.8
The first step of updating packages is to pull new changes to ~/.julia/METADATA and see if any new registered package versions have been published. After this, Pkg.update() attempts to update packages that are checked out on a branch and not dirty (i.e. no changes have been made to files tracked by git) by pulling changes from the package’s upstream repository. Upstream changes will only be applied if no merging or rebasing is necessary – i.e. if the branch can be “fast-forwarded”. If the branch cannot be fast-forwarded, it is assumed that you’re working on it and will update the repository yourself.
Finally, the update process recomputes an optimal set of package versions to have installed to satisfy your top-level requirements and the requirements of “fixed” packages. A package is considered fixed if it is one of the following:
If any of these are the case, the package manager cannot freely change the installed version of the package, so its requirements must be satisfied by whatever other package versions it picks. The combination of top-level requirements in ~/.julia/REQUIRE and the requirement of fixed packages are used to determine what should be installed.
You may want to use the master version of a package rather than one of its registered versions. There might be fixes or functionality on master that you need that aren’t yet published in any registered versions, or you may be a developer of the package and need to make changes on master or some other development branch. In such cases, you can do Pkg.checkout(pkg) to checkout the master branch of pkg or Pkg.checkout(pkg,branch) to checkout some other branch:
julia> Pkg.add("Distributions")
INFO: Installing Distributions v0.2.9
INFO: Installing NumericExtensions v0.2.17
INFO: Installing Stats v0.2.7
INFO: REQUIRE updated.
julia> Pkg.status()
Required packages:
- Distributions 0.2.9
Additional packages:
- NumericExtensions 0.2.17
- Stats 0.2.7
julia> Pkg.checkout("Distributions")
INFO: Checking out Distributions master...
INFO: No packages to install, update or remove.
julia> Pkg.status()
Required packages:
- Distributions 0.2.9+ master
Additional packages:
- NumericExtensions 0.2.17
- Stats 0.2.7
Immediately after installing Distributions with Pkg.add it is on the current most recent registered version – 0.2.9 at the time of writing this. Then after running Pkg.checkout("Distributions"), you can see from the output of Pkg.status() that Distributions is on an unregistered version greater than 0.2.9, indicated by the “pseudo-version” number 0.2.9+.
When you checkout an unregistered version of a package, the copy of the REQUIRE file in the package repo takes precedence over any requirements registered in METADATA, so it is important that developers keep this file accurate and up-to-date, reflecting the actual requirements of the current version of the package. If the REQUIRE file in the package repo is incorrect or missing, dependencies may be removed when the package is checked out. This file is also used to populate newly published versions of the package if you use the API that Pkg provides for this (described below).
When you decide that you no longer want to have a package checked out on a branch, you can “free” it back to the control of the package manager with Pkg.free(pkg):
julia> Pkg.free("Distributions")
INFO: Freeing Distributions...
INFO: No packages to install, update or remove.
julia> Pkg.status()
Required packages:
- Distributions 0.2.9
Additional packages:
- NumericExtensions 0.2.17
- Stats 0.2.7
After this, since the package is on a registered version and not on a branch, its version will be updated as new registered versions of the package are published.
If you want to pin a package at a specific version so that calling Pkg.update() won’t change the version the package is on, you can use the Pkg.pin function:
julia> Pkg.pin("Stats")
INFO: Creating Stats branch pinned.47c198b1.tmp
julia> Pkg.status()
Required packages:
- Distributions 0.2.9
Additional packages:
- NumericExtensions 0.2.17
- Stats 0.2.7 pinned.47c198b1.tmp
After this, the Stats package will remain pinned at version 0.2.7 – or more specifically, at commit 47c198b1, but since versions are permanently associated a given git hash, this is the same thing. Pkg.pin works by creating a throw-away branch for the commit you want to pin the package at and then checking that branch out. By default, it pins a package at the current commit, but you can choose a different version by passing a second argument:
julia> Pkg.pin("Stats",v"0.2.5")
INFO: Creating Stats branch pinned.1fd0983b.tmp
INFO: No packages to install, update or remove.
julia> Pkg.status()
Required packages:
- Distributions 0.2.9
Additional packages:
- NumericExtensions 0.2.17
- Stats 0.2.5 pinned.1fd0983b.tmp
Now the Stats package is pinned at commit 1fd0983b, which corresponds to version 0.2.5. When you decide to “unpin” a package and let the package manager update it again, you can use Pkg.free like you would to move off of any branch:
julia> Pkg.free("Stats")
INFO: Freeing Stats...
INFO: No packages to install, update or remove.
julia> Pkg.status()
Required packages:
- Distributions 0.2.9
Additional packages:
- NumericExtensions 0.2.17
- Stats 0.2.7
After this, the Stats package is managed by the package manager again, and future calls to Pkg.update() will upgrade it to newer versions when they are published. The throw-away pinned.1fd0983b.tmp branch remains in your local Stats repo, but since git branches are extremely lightweight, this doesn’t really matter; if you feel like cleaning them up, you can go into the repo and delete those branches.
[2] | Packages that aren’t on branches will also be marked as dirty if you make changes in the repo, but that’s a less common thing to do. |
Julia’s package manager is designed so that when you have a package installed, you are already in a position to look at its source code and full development history. You are also able to make changes to packages, commit them using git, and easily contribute fixes and enhancements upstream. Similarly, the system is designed so that if you want to create a new package, the simplest way to do so is within the infrastructure provided by the package manager.
Since packages are git repositories, before doing any package development you should setup the following standard global git configuration settings:
$ git config --global user.name "FULL NAME"
$ git config --global user.email "EMAIL"
where FULL NAME is your actual full name (spaces are allowed between the double quotes) and EMAIL is your actual email address. Although it isn’t necessary to use GitHub to create or publish Julia packages, most Julia packages as of writing this are hosted on GitHub and the package manager knows how to format origin URLs correctly and otherwise work with the service smoothly. We recommend that you create a free account on GitHub and then do:
$ git config --global github.user "USERNAME"
where USERNAME is your actual GitHub user name. Once you do this, the package manager knows your GitHub user name and can configure things accordingly. You should also upload your public SSH key to GitHub and set up an SSH agent on your development machine so that you can push changes with minimal hassle. In the future, we will make this system extensible and support other common git hosting options like BitBucket and allow developers to choose their favorite.
Suppose you want to create a new Julia package called FooBar. To get started, do Pkg.generate(pkg,license) where pkg is the new package name and license is the name of a license that the package generator knows about:
julia> Pkg.generate("FooBar","MIT")
INFO: Initializing FooBar repo: /Users/stefan/.julia/FooBar
INFO: Origin: git://github.com/StefanKarpinski/FooBar.jl.git
INFO: Generating LICENSE.md
INFO: Generating README.md
INFO: Generating src/FooBar.jl
INFO: Generating .travis.yml
INFO: Committing FooBar generated files
This creates the directory ~/.julia/FooBar, initializes it as a git repository, generates a bunch of files that all packages should have, and commits them to the repository:
$ cd ~/.julia/FooBar && git show --stat
commit 84b8e266dae6de30ab9703150b3bf771ec7b6285
Author: Stefan Karpinski <stefan@karpinski.org>
Date: Wed Oct 16 17:57:58 2013 -0400
FooBar.jl generated files.
license: MIT
authors: Stefan Karpinski
years: 2013
github: true
travis: true
Julia Version 0.2.0-rc1+23 [2039ec61a5]
.travis.yml | 13 +++++++++++++
LICENSE.md | 23 +++++++++++++++++++++++
README.md | 3 +++
src/FooBar.jl | 5 +++++
4 files changed, 44 insertions(+)
At the moment, the package manager knows about the MIT “Expat” License, indicated by "MIT", and the Simplified BSD License, indicated by "BSD". If you want to use a different license, you can ask us to add it to the package generator, or just pick one of these two and then modify the ~/.julia/PACKAGE/LICENSE.md file after it has been generated.
If you created a GitHub account and configured git to know about it, Pkg.generate will set an appropriate origin URL for you. It will also automatically generate a .travis.yml file for using the Travis automated testing service. You will have to enable testing on the Travis website for your package repository, but once you’ve done that, it will already have working tests. Of course, all the default testing does is verify that using FooBar in Julia works.
Once you’ve made some commits and you’re happy with how FooBar is working, you may want to get some other people to try it out. First you’ll need to create the remote repository and push your code to it; we don’t yet automatically do this for you, but we will in the future and it’s not too hard to figure out [3]. Once you’ve done this, letting people try out your code is as simple as sending them the URL of the published repo – in this case:
git://github.com/StefanKarpinski/FooBar.jl.git
For your package, it will be your GitHub user name and the name of your package, but you get the idea. People you send this URL to can use Pkg.clone to install the package and try it out:
julia> Pkg.clone("git://github.com/StefanKarpinski/FooBar.jl.git")
INFO: Cloning FooBar from git://github.com/StefanKarpinski/FooBar.jl.git
Cloning into 'FooBar'...
remote: Counting objects: 22, done.
remote: Compressing objects: 100% (12/12), done.
remote: Total 22 (delta 7), reused 21 (delta 6)
Receiving objects: 100% (22/22), done.
Resolving deltas: 100% (7/7), done.
Once you’ve decided that FooBar is ready to be registered as an official package, you can add it to your local copy of METADATA using Pkg.register:
julia> Pkg.register("FooBar")
INFO: Registering FooBar at git://github.com/StefanKarpinski/FooBar.jl.git
INFO: Committing METADATA for FooBar
This creates a commit in the ~/.julia/METADATA repo:
$ cd ~/.julia/METADATA && git show
commit 9f71f4becb05cadacb983c54a72eed744e5c019d
Author: Stefan Karpinski <stefan@karpinski.org>
Date: Wed Oct 16 18:46:02 2013 -0400
Register FooBar
diff --git a/FooBar/url b/FooBar/url
new file mode 100644
index 0000000..30e525e
--- /dev/null
+++ b/FooBar/url
@@ -0,0 +1 @@
+git://github.com/StefanKarpinski/FooBar.jl.git
This commit is only locally visible, however. In order to make it visible to the world, you need to merge your local METADATA upstream into the official repo. If you have push access to that repository (which we give to all package maintainers), then you can do so easily with the Pkg.publish() command, which publishes your local metadata changes. If you don’t have push access to METADATA, you’ll have to make a pull request on GitHub, which is not difficult.
Once the package URL for FooBar is registered in the official METADATA repo, people know where to clone the package from, but there still aren’t any registered versions available. This means that Pkg.add("FooBar") won’t work yet since it only installs official versions. People can, however, clone the package with just Pkg.clone("FooBar") without having to specify a URL for it. Moreover, when they run Pkg.update(), they will get the latest version of FooBar that you’ve pushed to the repo. This is a good way to have people test out your packages as you work on them, before they’re ready for an official release.
Once you are ready to make an official version your package, you can tag and register it with the Pkg.tag command:
julia> Pkg.tag("FooBar")
INFO: Tagging FooBar v0.0.0
INFO: Committing METADATA for FooBar
This tags v0.0.0 in the FooBar repo:
$ cd ~/.julia/FooBar && git tag
v0.0.0
It also creates a new version entry in your local METADATA repo for FooBar:
$ cd ~/.julia/FooBar && git show
commit de77ee4dc0689b12c5e8b574aef7f70e8b311b0e
Author: Stefan Karpinski <stefan@karpinski.org>
Date: Wed Oct 16 23:06:18 2013 -0400
Tag FooBar v0.0.0
diff --git a/FooBar/versions/0.0.0/sha1 b/FooBar/versions/0.0.0/sha1
new file mode 100644
index 0000000..c1cb1c1
--- /dev/null
+++ b/FooBar/versions/0.0.0/sha1
@@ -0,0 +1 @@
+84b8e266dae6de30ab9703150b3bf771ec7b6285
The Pkg.tag command takes an optional second argument that is either an explicit version number object like v"0.0.1" or one of the symbols :patch, :minor or :major. These increment the patch, minor or major version number of your package intelligently.
These changes to METADATA aren’t available to anyone else until they’ve been included upstream. If you have push access to the official METADATA repo, you can use the Pkg.publish() command, which first makes sure that individual package repos have been tagged, pushes them if they haven’t already been, and then pushes METADATA to the origin. If you don’t have push access to METADATA, you’ll have to open a pull request for the last bit, although we’re planning on automatically opening pull requests for you in the future.
If there is a REQUIRE file in your package repo, it will be copied into the appropriate spot in METADATA when you tag a version. Package developers should make sure that the REQUIRE file in their package correctly reflects the requirements of their package, which will automatically flow into the official metadata if you’re using Pkg.tag. If you need to fix the registered requirements of an already-published package version, you can do so just by editing the metadata for that version, which will still have the same commit hash – the hash associated with a version is permanent. Since the commit hash stays the same, the contents of the REQUIRE file that will be checked out in the repo will not match the requirements in METADATA after such a change; this is unavoidable. When you fix the requirements in METADATA for a previous version of a package, however, you should also fix the REQUIRE file in the current version of the package.
[3] | Installing and using GitHub’s “hub” tool is highly recommended. It allows you to do things like run hub create in the package repo and have it automatically created via GitHub’s API. |
The ~/.julia/REQUIRE file and REQUIRE files inside of packages use a simple line-based format to express what ranges of package versions are needed. Here’s how these files are parsed and interpreted. Everything after a # mark is stripped from each line as a comment. If nothing but whitespace is left, the line is ignored; if there are non-whitespace characters remaining, the line is a requirement and the is split on whitespace into words. The simplest possible requirement is just the name of a package name on a line by itself:
Distributions
This requirement is satisfied by any version of the Distributions package. The package name can be followed by zero or more version numbers in ascending order, indicating acceptable intervals of versions of that package. One version opens an interval, while the next closes it, and the next opens a new interval, and so on; if an odd number of version numbers are given, then arbitrarily large versions will satisfy; if an even number of version numbers are given, the last one is an upper limit on acceptable version numbers. For example, the line:
Distributions 0.1
is satisfied by any version of Distributions greater than or equal to 0.1.0. This requirement entry:
Distributions 0.1 0.2.5
is satisfied by versions from 0.1.0 up to, but not including 0.2.5. If you want to indicate that any 1.x version will do, you will want to write:
Distributions 0.1 0.2-
The 0.2- “pseudo-version” is less than all real version numbers that start with 0.2. If you want to start accepting versions after 0.2.7, you can write:
Distributions 0.1 0.2- 0.2.7
If a requirement line has leading words that begin with @, it is a system-dependent requirement. If your system matches these system conditionals, the requirement is included, if not, the requirement is ignored. For example:
@osx Homebrew
will require the Homebrew package only on systems where the operating system is OS X. The system conditions that are currently supported are:
@windows
@unix
@osx
@linux
The @unix condition is satisfied on all UNIX systems, including OS X, Linux and FreeBSD. Negated system conditionals are also supported by adding a ! after the leading @. Examples:
@!windows
@unix @!osx
The first condition applies to any system but Windows and the second condition applies to any UNIX system besides OS X.
In the following sections, we briefly go through a few techniques that can help make your Julia code run as fast as possible.
A global variable might have its value, and therefore its type, change at any point. This makes it difficult for the compiler to optimize code using global variables. Variables should be local, or passed as arguments to functions, whenever possible.
Any code that is performance-critical or being benchmarked should be inside a function.
We find that global names are frequently constants, and declaring them as such greatly improves performance:
const DEFAULT_VAL = 0
Uses of non-constant globals can be optimized by annotating their types at the point of use:
global x
y = f(x::Int + 1)
Writing functions is better style. It leads to more reusable code and clarifies what steps are being done, and what their inputs and outputs are.
When working with parameterized types, including arrays, it is best to avoid parameterizing with abstract types where possible.
Consider the following:
a = Real[] # typeof(a) = Array{Real,1}
if (f = rand()) < .8
push!(a, f)
end
Because a is a an array of abstract type Real, it must be able to hold any Real value. Since Real objects can be of arbitrary size and structure, a must be represented as an array of pointers to individually allocated Real objects. Because f will always be a Float64, we should instead, use:
a = Float64[] # typeof(a) = Array{Float64,1}
which will create a contiguous block of 64-bit floating-point values that can be manipulated efficiently.
See also the discussion under Parametric Types.
In many languages with optional type declarations, adding declarations is the principal way to make code run faster. This is not the case in Julia. In Julia, the compiler generally knows the types of all function arguments, local variables, and expressions. However, there are a few specific instances where declarations are helpful.
Given a user-defined type like the following:
type Foo
field
end
the compiler will not generally know the type of foo.field, since it might be modified at any time to refer to a value of a different type. It will help to declare the most specific type possible, such as field::Float64 or field::Array{Int64,1}.
It is often convenient to work with data structures that may contain values of any type, such as the original Foo type above, or cell arrays (arrays of type Array{Any}). But, if you’re using one of these structures and happen to know the type of an element, it helps to share this knowledge with the compiler:
function foo(a::Array{Any,1})
x = a[1]::Int32
b = x+1
...
end
Here, we happened to know that the first element of a would be an Int32. Making an annotation like this has the added benefit that it will raise a run-time error if the value is not of the expected type, potentially catching certain bugs earlier.
Keyword arguments can have declared types:
function with_keyword(x; name::Int = 1)
...
end
Functions are specialized on the types of keyword arguments, so these declarations will not affect performance of code inside the function. However, they will reduce the overhead of calls to the function that include keyword arguments.
Functions with keyword arguments have near-zero overhead for call sites that pass only positional arguments.
Passing dynamic lists of keyword arguments, as in f(x; keywords...), can be slow and should be avoided in performance-sensitive code.
Writing a function as many small definitions allows the compiler to directly call the most applicable code, or even inline it.
Here is an example of a “compound function” that should really be written as multiple definitions:
function norm(A)
if isa(A, Vector)
return sqrt(real(dot(x,x)))
elseif isa(A, Matrix)
return max(svd(A)[2])
else
error("norm: invalid argument")
end
end
This can be written more concisely and efficiently as:
norm(A::Vector) = sqrt(real(dot(x,x)))
norm(A::Matrix) = max(svd(A)[2])
When possible, it helps to ensure that a function always returns a value of the same type. Consider the following definition:
pos(x) = x < 0 ? 0 : x
Although this seems innocent enough, the problem is that 0 is an integer (of type Int) and x might be of any type. Thus, depending on the value of x, this function might return a value of either of two types. This behavior is allowed, and may be desirable in some cases. But it can easily be fixed as follows:
pos(x) = x < 0 ? zero(x) : x
There is also a one function, and a more general oftype(x,y) function, which returns y converted to the type of x. The first argument to any of these functions can be either a value or a type.
An analogous “type-stability” problem exists for variables used repeatedly within a function:
function foo()
x = 1
for i = 1:10
x = x/bar()
end
return x
end
Local variable x starts as an integer, and after one loop iteration becomes a floating-point number (the result of the / operator). This makes it more difficult for the compiler to optimize the body of the loop. There are several possible fixes:
Many functions follow a pattern of performing some set-up work, and then running many iterations to perform a core computation. Where possible, it is a good idea to put these core computations in separate functions. For example, the following contrived function returns an array of a randomly-chosen type:
function strange_twos(n)
a = Array(randbool() ? Int64 : Float64, n)
for i = 1:n
a[i] = 2
end
return a
end
This should be written as:
function fill_twos!(a)
for i=1:length(a)
a[i] = 2
end
end
function strange_twos(n)
a = Array(randbool() ? Int64 : Float64, n)
fill_twos!(a)
return a
end
Julia’s compiler specializes code for argument types at function boundaries, so in the original implementation it does not know the type of a during the loop (since it is chosen randomly). Therefore the second version is generally faster since the inner loop can be recompiled as part of fill_twos! for different types of a.
The second form is also often better style and can lead to more code reuse.
This pattern is used in several places in the standard library. For example, see hvcat_fill in abstractarray.jl, or the fill! function, which we could have used instead of writing our own fill_twos!.
Functions like strange_twos occur when dealing with data of uncertain type, for example data loaded from an input file that might contain either integers, floats, strings, or something else.
A deprecated function internally performs a lookup in order to print a relevant warning only once. This extra lookup can cause a significant slowdown, so all uses of deprecated functions should be modified as suggested by the warnings.
These are some minor points that might help in tight inner loops.
The following sections explain a few aspects of idiomatic Julia coding style. None of these rules are absolute; they are only suggestions to help familiarize you with the language and to help you choose among alternative designs.
Writing code as a series of steps at the top level is a quick way to get started solving a problem, but you should try to divide a program into functions as soon as possible. Functions are more reusable and testable, and clarify what steps are being done and what their inputs and outputs are. Furthermore, code inside functions tends to run much faster than top level code, due to how Julia’s compiler works.
It is also worth emphasizing that functions should take arguments, instead of operating directly on global variables (aside from constants like pi).
Code should be as generic as possible. Instead of writing:
convert(Complex{Float64}, x)
it’s better to use available generic functions:
complex(float(x))
The second version will convert x to an appropriate type, instead of always the same type.
This style point is especially relevant to function arguments. For example, don’t declare an argument to be of type Int or Int32 if it really could be any integer, expressed with the abstract type Integer. In fact, in many cases you can omit the argument type altogether, unless it is needed to disambiguate from other method definitions, since a MethodError will be thrown anyway if a type is passed that does not support any of the requisite operations. (This is known as duck typing.)
For example, consider the following definitions of a function addone that returns one plus its argument:
addone(x::Int) = x + 1 # works only for Int
addone(x::Integer) = x + one(x) # any integer type
addone(x::Number) = x + one(x) # any numeric type
addone(x) = x + one(x) # any type supporting + and one
The last definition of addone handles any type supporting the one function (which returns 1 in the same type as x, which avoids unwanted type promotion) and the + function with those arguments. The key thing to realize is that there is no performance penalty to defining only the general addone(x) = x + one(x), because Julia will automatically compile specialized versions as needed. For example, the first time you call addone(12), Julia will automatically compile a specialized addone function for x::Int arguments, with the call to one replaced by its inlined value 1. Therefore, the first three definitions of addone above are completely redundant.
Instead of:
function foo(x, y)
x = int(x); y = int(y)
...
end
foo(x, y)
use:
function foo(x::Int, y::Int)
...
end
foo(int(x), int(y))
This is better style because foo does not really accept numbers of all types; it really needs Int s.
One issue here is that if a function inherently requires integers, it might be better to force the caller to decide how non-integers should be converted (e.g. floor or ceiling). Another issue is that declaring more specific types leaves more “space” for future method definitions.
Instead of:
function double{T<:Number}(a::AbstractArray{T})
for i = 1:endof(a); a[i] *= 2; end
a
end
use:
function double!{T<:Number}(a::AbstractArray{T})
for i = 1:endof(a); a[i] *= 2; end
a
end
The Julia standard library uses this convention throughout and contains examples of functions with both copying and modifying forms (e.g., sort and sort!), and others which are just modifying (e.g., push!, pop!, splice!). It is typical for such functions to also return the modified array for convenience.
Types such as Union(Function,String) are often a sign that some design could be cleaner.
When using x::Union(Nothing,T), ask whether the option for x to be nothing is really necessary. Here are some alternatives to consider:
It is usually not much help to construct arrays like the following:
a = Array(Union(Int,String,Tuple,Array), n)
In this case cell(n) is better. It is also more helpful to the compiler to annotate specific uses (e.g. a[i]::Int) than to try to pack many alternatives into one type.
If a function name requires multiple words, it might represent more than one concept. It is better to keep identifier names concise.
It is better to avoid errors than to rely on catching them.
Julia doesn’t require parens around conditions in if and while. Write:
if a == b
instead of:
if (a == b)
Splicing function arguments can be addictive. Instead of [a..., b...], use simply [a, b], which already concatenates arrays. collect(a) is better than [a...], but since a is already iterable it is often even better to leave it alone, and not convert it to an array.
A function signature:
foo{T<:Real}(x::T) = ...
should be written as:
foo(x::Real) = ...
instead, especially if T is not used in the function body. If T is used, it can be replaced with typeof(x) if convenient. There is no performance difference. Note that this is not a general caution against static parameters, just against uses where they are not needed.
Sets of definitions like the following are confusing:
foo(::Type{MyType}) = ...
foo(::MyType) = foo(MyType)
Decide whether the concept in question will be written as MyType or MyType(), and stick to it.
The preferred style is to use instances by default, and only add methods involving Type{MyType} later if they become necessary to solve some problem.
If a type is effectively an enumeration, it should be defined as a single (ideally immutable) type, with the enumeration values being instances of it. Constructors and conversions can check whether values are valid. This design is preferred over making the enumeration an abstract type, with the values as subtypes.
Be aware of when a macro could really be a function instead.
Calling eval inside a macro is a particularly dangerous warning sign; it means the macro will only work when called at the top level. If such a macro is written as a function instead, it will naturally have access to the run-time values it needs.
If you have a type that uses a native pointer:
type NativeType
p::Ptr{Uint8}
...
end
don’t write definitions like the following:
getindex(x::NativeType, i) = unsafe_load(x.p, i)
The problem is that users of this type can write x[i] without realizing that the operation is unsafe, and then be susceptible to memory bugs.
Such a function should either check the operation to ensure it is safe, or have unsafe somewhere in its name to alert callers.
It is possible to write definitions like the following:
show(io::IO, v::Vector{MyType}) = ...
This would provide custom showing of vectors with a specific new element type. While tempting, this should be avoided. The trouble is that users will expect a well-known type like Vector to behave in a certain way, and overly customizing its behavior can make it harder to work with.
You generally want to use isa and <: (subtype) for testing types, not ==. Checking types for exact equality typically only makes sense when comparing to a known concrete type (e.g. T == Float64), or if you really, really know what you’re doing.
Since higher-order functions are often called with anonymous functions, it is easy to conclude that this is desirable or even necessary. But any function can be passed directly, without being “wrapped” in an anonymous function. Instead of writing map(x->f(x), a), write map(f, a).
Julia does not have an analog of MATLAB’s clear function; once a name is defined in a Julia session (technically, in module Main), it is always present.
If memory usage is your concern, you can always replace objects with ones that consume less memory. For example, if A is a gigabyte-sized array that you no longer need, you can free the memory with A = 0. The memory will be released the next time the garbage collector runs; you can force this to happen with gc().
Perhaps you’ve defined a type and then realize you need to add a new field. If you try this at the REPL, you get the error:
ERROR: invalid redefinition of constant MyType
Types in module Main cannot be redefined.
While this can be inconvenient when you are developing new code, there’s an excellent workaround. Modules can be replaced by redefining them, and so if you wrap all your new code inside a module you can redefine types and constants. You can’t import the type names into Main and then expect to be able to redefine them there, but you can use the module name to resolve the scope. In other words, while developing you might use a workflow something like this:
include("mynewcode.jl") # this defines a module MyModule
obj1 = MyModule.ObjConstructor(a, b)
obj2 = MyModule.somefunction(obj1)
# Got an error. Change something in "mynewcode.jl"
include("mynewcode.jl") # reload the module
obj1 = MyModule.ObjConstructor(a, b) # old objects are no longer valid, must reconstruct
obj2 = MyModule.somefunction(obj1) # this time it worked!
obj3 = MyModule.someotherfunction(obj2, c)
...
Types can be declared without specifying the types of their fields:
type MyAmbiguousType
a
end
This allows a to be of any type. This can often be useful, but it does have a downside: for objects of type MyAmbiguousType, the compiler will not be able to generate high-performance code. The reason is that the compiler uses the types of objects, not their values, to determine how to build code. Unfortunately, very little can be inferred about an object of type MyAmbiguousType:
julia> b = MyAmbiguousType("Hello")
MyAmbiguousType("Hello")
julia> c = MyAmbiguousType(17)
MyAmbiguousType(17)
julia> typeof(b)
MyAmbiguousType
julia> typeof(c)
MyAmbiguousType
b and c have the same type, yet their underlying representation of data in memory is very different. Even if you stored just numeric values in field a, the fact that the memory representation of a Uint8 differs from a Float64 also means that the CPU needs to handle them using two different kinds of instructions. Since the required information is not available in the type, such decisions have to be made at run-time. This slows performance.
You can do better by declaring the type of a. Here, we are focused on the case where a might be any one of several types, in which case the natural solution is to use parameters. For example:
type MyType{T<:FloatingPoint}
a::T
end
This is a better choice than
type MyStillAmbiguousType
a::FloatingPoint
end
because the first version specifies the type of a from the type of the wrapper object. For example:
julia> m = MyType(3.2)
MyType{Float64}(3.2)
julia> t = MyStillAmbiguousType(3.2)
MyStillAmbiguousType(3.2)
julia> typeof(m)
MyType{Float64}
julia> typeof(t)
MyStillAmbiguousType
The type of field a can be readily determined from the type of m, but not from the type of t. Indeed, in t it’s possible to change the type of field a:
julia> typeof(t.a)
Float64
julia> t.a = 4.5f0
4.5f0
julia> typeof(t.a)
Float32
In contrast, once m is constructed, the type of m.a cannot change:
julia> m.a = 4.5f0
4.5
julia> typeof(m.a)
Float64
The fact that the type of m.a is known from m‘s type—coupled with the fact that its type cannot change mid-function—allows the compiler to generate highly-optimized code for objects like m but not for objects like t.
Of course, all of this is true only if we construct m with a concrete type. We can break this by explicitly constructing it with an abstract type:
julia> m = MyType{FloatingPoint}(3.2)
MyType{FloatingPoint}(3.2)
julia> typeof(m.a)
Float64
julia> m.a = 4.5f0
4.5f0
julia> typeof(m.a)
Float32
For all practical purposes, such objects behave identically to those of MyStillAmbiguousType.
It’s quite instructive to compare the sheer amount code generated for a simple function
func(m::MyType) = m.a+1
using
code_llvm(func,(MyType{Float64},))
code_llvm(func,(MyType{FloatingPoint},))
code_llvm(func,(MyType,))
For reasons of length the results are not shown here, but you may wish to try this yourself. Because the type is fully-specified in the first case, the compiler doesn’t need to generate any code to resolve the type at run-time. This results in shorter and faster code.
The same best practices that apply in the previous section also work for container types:
type MySimpleContainer{A<:AbstractVector}
a::A
end
type MyAmbiguousContainer{T}
a::AbstractVector{T}
end
For example:
julia> c = MySimpleContainer(1:3);
julia> typeof(c)
MySimpleContainer{Range1{Int64}}
julia> c = MySimpleContainer([1:3]);
julia> typeof(c)
MySimpleContainer{Array{Int64,1}}
julia> b = MyAmbiguousContainer(1:3);
julia> typeof(b)
MyAmbiguousContainer{Int64}
julia> b = MyAmbiguousContainer([1:3]);
julia> typeof(b)
MyAmbiguousContainer{Int64}
For MySimpleContainer, the object is fully-specified by its type and parameters, so the compiler can generate optimized functions. In most instances, this will probably suffice.
While the compiler can now do its job perfectly well, there are cases where you might wish that your code could do different things depending on the element type of a. Usually the best way to achieve this is to wrap your specific operation (here, foo) in a separate function:
function sumfoo(c::MySimpleContainer)
s = 0
for x in c.a
s += foo(x)
end
s
end
foo(x::Integer) = x
foo(x::FloatingPoint) = round(x)
This keeps things simple, while allowing the compiler to generate optimized code in all cases.
However, there are cases where you may need to declare different versions of the outer function for different element types of a. You could do it like this:
function myfun{T<:FloatingPoint}(c::MySimpleContainer{Vector{T}})
...
end
function myfun{T<:Integer}(c::MySimpleContainer{Vector{T}})
...
end
This works fine for Vector{T}, but we’d also have to write explicit versions for Range1{T} or other abstract types. To prevent such tedium, you can use two parameters in the declaration of MyContainer:
type MyContainer{T, A<:AbstractVector}
a::A
end
MyContainer(v::AbstractVector) = MyContainer{eltype(v), typeof(v)}(v)
julia> b = MyContainer(1.3:5);
julia> typeof(b)
MyContainer{Float64,Range1{Float64}}
Note the somewhat surprising fact that T doesn’t appear in the declaration of field a, a point that we’ll return to in a moment. With this approach, one can write functions such as:
function myfunc{T<:Integer, A<:AbstractArray}(c::MyContainer{T,A})
return c.a[1]+1
end
# Note: because we can only define MyContainer for
# A<:AbstractArray, and any unspecified parameters are arbitrary,
# the previous could have been written more succinctly as
# function myfunc{T<:Integer}(c::MyContainer{T})
function myfunc{T<:FloatingPoint}(c::MyContainer{T})
return c.a[1]+2
end
function myfunc{T<:Integer}(c::MyContainer{T,Vector{T}})
return c.a[1]+3
end
julia> myfunc(MyContainer(1:3))
2
julia> myfunc(MyContainer(1.0:3))
3.0
julia> myfunc(MyContainer([1:3]))
4
As you can see, with this approach it’s possible to specialize on both the element type T and the array type A.
However, there’s one remaining hole: we haven’t enforced that A has element type T, so it’s perfectly possible to construct an object like this:
julia> b = MyContainer{Int64, Range1{Float64}}(1.3:5);
julia> typeof(b)
MyContainer{Int64,Range1{Float64}}
To prevent this, we can add an inner constructor:
type MyBetterContainer{T<:Real, A<:AbstractVector}
a::A
MyBetterContainer(v::AbstractVector{T}) = new(v)
end
MyBetterContainer(v::AbstractVector) = MyBetterContainer{eltype(v),typeof(v)}(v)
julia> b = MyBetterContainer(1.3:5);
julia> typeof(b)
MyBetterContainer{Float64,Range1{Float64}}
julia> b = MyBetterContainer{Int64, Range1{Float64}}(1.3:5);
ERROR: no method MyBetterContainer(Range1{Float64},)
The inner constructor requires that the element type of A be T.
Unlike many languages (for example, C and Java), Julia does not have a “null” value. When a reference (variable, object field, or array element) is uninitialized, accessing it will immediately throw an error. This situation can be detected using the isdefined function.
Some functions are used only for their side effects, and do not need to return a value. In these cases, the convention is to return the value nothing, which is just a singleton object of type Nothing. This is an ordinary type with no fields; there is nothing special about it except for this convention, and that the REPL does not print anything for it. Some language constructs that would not otherwise have a value also yield nothing, for example if false; end.
Note that Nothing (uppercase) is the type of nothing, and should only be used in a context where a type is required (e.g. a declaration).
You may occasionally see None, which is quite different. It is the empty (or “bottom”) type, a type with no values and no subtypes (except itself). You will generally not need to use this type.
The empty tuple (()) is another form of nothingness. But, it should not really be thought of as nothing but rather a tuple of zero values.
You may prefer the release version of Julia if you are looking for a stable code base. Releases generally occur every 6 months, giving you a stable platform for writing code.
You may prefer the beta version of Julia if you don’t mind being slightly behind the latest bugfixes and changes, but find the slightly slower rate of changes more appealing. Additionally, these binaries are tested before they are published to ensure they are fully functional.
You may prefer the nightly version of Julia if you want to take advantage of the latest updates to the language, and don’t mind if the version available today occasionally doesn’t actually work.
Finally, you may also consider building Julia from source for yourself. This option is mainly for those individuals who are comfortable at the command line, or interested in learning. If this describes you, you may also be interested in reading our guidelines for contributing.
Links to each of these download types can be found on the download page at http://julialang.org/downloads/. Note that not all versions of Julia are available for all platforms.
Deprecated functions are removed after the subsequent release. For example, functions marked as deprecated in the 0.1 release will not be available starting with the 0.2 release.
First, you should build the debug version of julia with make debug. Below, lines starting with (gdb) mean things you should type at the gdb prompt.
The main challenge is that Julia and gdb each need to have their own terminal, to allow you to interact with them both. One approach is to use gdb’s attach functionality to debug an already-running julia session. However, on many systems you’ll need root access to get this to work. What follows is a method that can be implemented with just user-level permissions.
The first time you do this, you’ll need to define a script, here called oterm, containing the following lines:
ps
sleep 600000
Make it executable with chmod +x oterm.
Now:
Although MATLAB users may find Julia’s syntax familiar, Julia is in no way a MATLAB clone. There are major syntactic and functional differences. The following are some noteworthy differences that may trip up Julia users accustomed to MATLAB:
One of Julia’s goals is to provide an effective language for data analysis and statistical programming. For users coming to Julia from R, these are some noteworthy differences:
Julia uses = for assignment. Julia does not provide any operator like <- or <<-.
Julia constructs vectors using brackets. Julia’s [1, 2, 3] is the equivalent of R’s c(1, 2, 3).
Julia’s matrix operations are more like traditional mathematical notation than R’s. If A and B are matrices, then A * B defines a matrix multiplication in Julia equivalent to R’s A %*% B. In R, this same notation would perform an elementwise Hadamard product. To get the elementwise multiplication operation, you need to write A .* B in Julia.
Julia performs matrix transposition using the ' operator. Julia’s A' is therefore equivalent to R’s t(A).
Julia does not require parentheses when writing if statements or for loops: use for i in [1, 2, 3] instead of for (i in c(1, 2, 3)) and if i == 1 instead of if (i == 1).
Julia does not treat the numbers 0 and 1 as Booleans. You cannot write if (1) in Julia, because if statements accept only booleans. Instead, you can write if true.
Julia does not provide nrow and ncol. Instead, use size(M, 1) for nrow(M) and size(M, 2) for ncol(M).
Julia’s SVD is not thinned by default, unlike R. To get results like R’s, you will often want to call svd(X, true) on a matrix X.
Julia is very careful to distinguish scalars, vectors and matrices. In R, 1 and c(1) are the same. In Julia, they can not be used interchangeably. One potentially confusing result of this is that x' * y for vectors x and y is a 1-element vector, not a scalar. To get a scalar, use dot(x, y).
Julia’s diag() and diagm() are not like R’s.
Julia cannot assign to the results of function calls on the left-hand of an assignment operation: you cannot write diag(M) = ones(n).
Julia provides tuples and real hash tables, but not R’s lists. When returning multiple items, you should typically use a tuple: instead of list(a = 1, b = 2), use (1, 2).
Julia encourages all users to write their own types. Julia’s types are much easier to use than S3 or S4 objects in R. Julia’s multiple dispatch system means that table(x::TypeA) and table(x::TypeB) act like R’s table.TypeA(x) and table.TypeB(x).
In Julia, values are passed and assigned by reference. If a function modifies an array, the changes will be visible in the caller. This is very different from R and allows new functions to operate on large data structures much more efficiently.
Concatenation of vectors and matrices is done using hcat and vcat, not c, rbind and cbind.
A Julia range object like a:b is not shorthand for a vector like in R, but is a specialized type of object that is used for iteration without high memory overhead. To convert a range into a vector, you need to wrap the range with brackets [a:b].
Julia has several functions that can mutate their arguments. For example, it has sort(v) and sort!(v).
colMeans() and rowMeans(), size(m, 1) and size(m, 2)
In R, performance requires vectorization. In Julia, almost the opposite is true: the best performing code is often achieved by using devectorized loops.
Unlike R, there is no delayed evaluation in Julia. For most users, this means that there are very few unquoted expressions or column names.
Julia does not support the NULL type.
There is no equivalent of R’s assign or get in Julia.
The Julia standard library contains a range of functions and macros appropriate for performing scientific and numerical computing, but as broad as many general purpose programming languages. Additional functionality is available from a growing collection of Available Packages. Functions are grouped by topic below.
Some general notes:
Quit (or control-D at the prompt). The default exit code is zero, indicating that the processes completed successfully.
Calls exit(0).
Register a zero-argument function to be called at exit.
Determine whether Julia is running an interactive session.
Print information about global variables in a module, optionally restricted to those matching pattern.
Edit a file optionally providing a line number to edit at. Returns to the julia prompt when you quit the editor.
Edit the definition of a function, optionally specifying a tuple of types to indicate which method to edit.
Show a file using the default pager, optionally providing a starting line number. Returns to the julia prompt when you quit the pager.
Show the definition of a function using the default pager, optionally specifying a tuple of types to indicate which method to see.
Send a printed form of x to the operating system clipboard (“copy”).
Return the contents of the operating system clipboard (“paste”).
Load source files once, in the context of the Main module, on every active node, searching the system-wide LOAD_PATH for files. require is considered a top-level operation, so it sets the current include path but does not use it to search for files (see help for include). This function is typically used to load library code, and is implicitly called by using to load packages.
Like require, except forces loading of files regardless of whether they have been loaded before. Typically used when interactively developing libraries.
Evaluate the contents of a source file in the current context. During including, a task-local include path is set to the directory containing the file. Nested calls to include will search relative to that path. All paths refer to files on node 1 when running in parallel, and files will be fetched from node 1. This function is typically used to load source interactively, or to combine files in packages that are broken into multiple source files.
Like include, except reads code from the given string rather than from a file. Since there is no file path involved, no path processing or fetching from node 1 is done.
Get help for a function. name can be an object or a string.
Search documentation for functions related to string.
Show which method of f will be called for the given arguments.
Evaluates the arguments to the function call, determines their types, and calls the which function on the resulting expression
Show all methods of f with their argument types.
Show all methods with an argument of type typ. If optional showparents is true, also show arguments with a parent type of typ, excluding type Any.
Show an expression and result, returning the result
Print information about the version of Julia in use. If the verbose argument is true, detailed system information is shown as well.
Determine whether x and y are identical, in the sense that no program could distinguish them. Compares mutable objects by address in memory, and compares immutable objects (such as numbers) by contents at the bit level. This function is sometimes called egal. The === operator is an alias for this function.
Determine whether x is of the given type.
True if and only if x and y have the same contents. Loosely speaking, this means x and y would look the same when printed. This is the default comparison function used by hash tables (Dict). New types with a notion of equality should implement this function, except for numbers, which should implement == instead. However, numeric types with special values might need to implement isequal as well. For example, floating point NaN values are not ==, but are all equivalent in the sense of isequal. Numbers of different types are considered unequal. Mutable containers should generally implement isequal by calling isequal recursively on all contents.
Test whether x is less than y. Provides a total order consistent with isequal. Values that are normally unordered, such as NaN, are ordered in an arbitrary but consistent fashion. This is the default comparison used by sort. Non-numeric types that can be ordered should implement this function. Numeric types only need to implement it if they have special values such as NaN.
Return x if condition is true, otherwise return y. This differs from ? or if in that it is an ordinary function, so all the arguments are evaluated first.
Compare x and y lexicographically and return -1, 0, or 1 depending on whether x is less than, equal to, or greater than y, respectively. This function should be defined for lexicographically comparable types, and lexless will call lexcmp by default.
Determine whether x is lexicographically less than y.
Get the concrete type of x.
Construct a tuple of the given objects.
Create a tuple of length n, computing each element as f(i), where i is the index of the element.
Get a unique integer id for x. object_id(x)==object_id(y) if and only if is(x,y).
Compute an integer hash code such that isequal(x,y) implies hash(x)==hash(y).
Register a function f(x) to be called when there are no program-accessible references to x. The behavior of this function is unpredictable if x is of a bits type.
Create a shallow copy of x: the outer structure is copied, but not all internal values. For example, copying an array produces a new array with identically-same elements as the original.
Create a deep copy of x: everything is copied recursively, resulting in a fully independent object. For example, deep-copying an array produces a new array whose elements are deep-copies of the original elements.
As a special case, functions can only be actually deep-copied if they are anonymous, otherwise they are just copied. The difference is only relevant in the case of closures, i.e. functions which may contain hidden internal references.
While it isn’t normally necessary, user-defined types can override the default deepcopy behavior by defining a specialized version of the function deepcopy_internal(x::T, dict::ObjectIdDict) (which shouldn’t otherwise be used), where T is the type to be specialized for, and dict keeps track of objects copied so far within the recursion. Within the definition, deepcopy_internal should be used in place of deepcopy, and the dict variable should be updated as appropriate before returning.
Tests whether an assignable location is defined. The arguments can be an array and index, a composite object and field name (as a symbol), or a module and a symbol.
Try to convert x to the given type. Conversions from floating point to integer, rational to integer, and complex to real will raise an InexactError if x cannot be represented exactly in the new type.
Convert all arguments to their common promotion type (if any), and return them all (as a tuple).
Convert y to the type of x.
The identity function. Returns its argument.
Return the supertype of DataType T
True if and only if all values of type1 are also of type2. Can also be written using the <: infix operator as type1 <: type2.
Subtype operator, equivalent to issubtype(T1,T2).
Return a list of immediate subtypes of DataType T. Note that all currently loaded subtypes are included, including those not visible in the current module.
Return a nested list of all subtypes of DataType T. Note that all currently loaded subtypes are included, including those not visible in the current module.
The lowest value representable by the given (real) numeric type.
The highest value representable by the given (real) numeric type.
The smallest in absolute value non-subnormal value representable by the given floating-point type
The highest finite value representable by the given floating-point type
The largest integer losslessly representable by the given floating-point type
Size, in bytes, of the canonical binary representation of the given type, if any.
The distance between 1.0 and the next larger representable floating-point value of type. The only types that are sensible arguments are Float32 and Float64. If type is omitted, then eps(Float64) is returned.
The distance between x and the next larger representable floating-point value of the same type as x.
Determine a type big enough to hold values of each argument type without loss, whenever possible. In some cases, where no type exists which to which both types can be promoted losslessly, some loss is tolerated; for example, promote_type(Int64,Float64) returns Float64 even though strictly, not all Int64 values can be represented exactly as Float64 values.
Specifies what type should be used by promote when given values of types type1 and type2. This function should not be called directly, but should have definitions added to it for new types as appropriate.
Extract a named field from a value of composite type. The syntax a.b calls getfield(a, :b), and the syntax a.(b) calls getfield(a, b).
Assign x to a named field in value of composite type. The syntax a.b = c calls setfield(a, :b, c), and the syntax a.(b) = c calls setfield(a, b, c).
The byte offset of each field of a type relative to the data start. For example, we could use it in the following manner to summarize information about a struct type:
structinfo(T) = [zip(fieldoffsets(T),names(T),T.types)...]
structinfo(Stat)
Determine the declared type of a named field in a value of composite type.
True if value v is immutable. See Immutable Composite Types for a discussion of immutability.
True if T is a “plain data” type, meaning it is immutable and contains no references to other values. Typical examples are numeric types such as Uint8, Float64, and Complex{Float64}.
Determine whether T is a concrete type that can have instances, meaning its only subtypes are itself and None (but T itself is not None).
Compute a type that contains both T and S.
Compute a type that contains the intersection of T and S. Usually this will be the smallest such type or one close to it.
Determine whether the given generic function has a method matching the given tuple of argument types.
Example: method_exists(length, (Array,)) = true
Determine whether the given generic function has a method applicable to the given arguments.
Invoke a method for the given generic function matching the specified types (as a tuple), on the specified arguments. The arguments must be compatible with the specified types. This allows invoking a method other than the most specific matching method, which is useful when the behavior of a more general definition is explicitly needed (often as part of the implementation of a more specific method of the same function).
Applies a function to the preceding argument which allows for easy function chaining.
Example: [1:5] |> x->x.^2 |> sum |> inv
Evaluate an expression and return the value.
Evaluate an expression and return the value.
Evaluate all expressions in the given file, and return the value of the last one. No other processing (path searching, fetching from node 1, etc.) is performed.
Only valid in the context of an Expr returned from a macro. Prevents the macro hygine pass from turning embedded variables into gensym variables. See the Macros section of the Metaprogramming chapter of the manual for more details and examples.
Generates a symbol which will not conflict with other variable names.
Generates a gensym symbol for a variable. For example, @gensym x y is transformed into x = gensym(“x”); y = gensym(“y”).
Parse the expression string and return an expression (which could later be passed to eval for execution). Start is the index of the first character to start parsing (default is 1). If greedy is true (default), parse will try to consume as much input as it can; otherwise, it will stop as soon as it has parsed a valid token. If raise is true (default), parse errors will raise an error; otherwise, parse will return the error as an expression object.
Sequential iteration is implemented by the methods start, done, and next. The general for loop:
for i = I
# body
end
is translated to:
state = start(I)
while !done(I, state)
(i, state) = next(I, state)
# body
end
The state object may be anything, and should be chosen appropriately for each iterable type.
Get initial iteration state for an iterable object
Test whether we are done iterating
For a given iterable object and iteration state, return the current item and the next iteration state
For a set of iterable objects, returns an iterable of tuples, where the ith tuple contains the ith component of each input iterable.
Note that zip is it’s own inverse: [zip(zip(a...)...)...] == [a...].
Return an iterator that yields (i, x) where i is an index starting at 1, and x is the ith value from the given iterator.
Fully implemented by: Range, Range1, NDRange, Tuple, Real, AbstractArray, IntSet, ObjectIdDict, Dict, WeakKeyDict, EachLine, String, Set, Task.
Determine whether a collection is empty (has no elements).
Remove all elements from a collection.
For ordered, indexable collections, the maximum index i for which getindex(collection, i) is valid. For unordered collections, the number of elements.
Returns the last index of the collection.
Example: endof([1,2,4]) = 3
Fully implemented by: Range, Range1, Tuple, Number, AbstractArray, IntSet, Dict, WeakKeyDict, String, Set.
Determine whether an item is in the given collection, in the sense that it is isequal to one of the values generated by iterating over the collection.
Determine the type of the elements generated by iterating collection. For associative collections, this will be a (key,value) tuple type.
Returns a vector containing the highest index in b for each value in a that is a member of b . The output vector contains 0 wherever a is not a member of b.
Returns the indices of elements in collection a that appear in collection b
Returns an array containing only the unique elements of the iterable itr, in the order that the first of each set of equivalent elements originally appears.
Reduce the given collection with the given operator, i.e. accumulate v = op(v,elt) for each element, where v starts as v0. Reductions for certain commonly-used operators are available in a more convenient 1-argument form: maximum(itr), minimum(itr), sum(itr), prod(itr), any(itr), all(itr).
The associativity of the reduction is implementation-dependent; if you need a particular associativity, e.g. left-to-right, you should write your own loop.
Returns the largest element in a collection
Compute the maximum value of an array over the given dimensions
Returns the smallest element in a collection
Compute the minimum value of an array over the given dimensions
Returns the index of the maximum element in a collection
Returns the index of the minimum element in a collection
Returns the maximum element and its index
Returns the minimum element and its index
Returns the sum of all elements in a collection
Sum elements of an array over the given dimensions.
Sum the results of calling function f on each element of itr.
Returns the product of all elements of a collection
Multiply elements of an array over the given dimensions.
Test whether any elements of a boolean collection are true
Test whether any values along the given dimensions of an array are true.
Test whether all elements of a boolean collection are true
Test whether all values along the given dimensions of an array are true.
Count the number of elements in itr for which predicate p is true.
Determine whether any element of itr satisfies the given predicate.
Determine whether all elements of itr satisfy the given predicate.
Transform collection c by applying f to each element.
Example: map((x) -> x * 2, [1, 2, 3]) = [2, 4, 6]
Applies function f to each element in itr and then reduces the result using the binary function op.
Example: mapreduce(x->x^2, +, [1:3]) == 1 + 4 + 9 == 14
The associativity of the reduction is implementation-dependent; if you need a particular associativity, e.g. left-to-right, you should write your own loop.
Get the first element of an iterable collection.
Get the last element of an ordered collection, if it can be computed in O(1) time. This is accomplished by calling endof to get the last index.
Get the step size of a Range object.
Return an array of all items in a collection. For associative collections, returns (key, value) tuples.
Return an array of type Array{element_type,1} of all items in a collection.
Determine whether every element of a is also in b, using the in function.
Return a copy of collection, removing elements for which function is false. For associative collections, the function is passed two arguments (key and value).
Update collection, removing elements for which function is false. For associative collections, the function is passed two arguments (key and value).
Retrieve the value(s) stored at the given key or index within a collection. The syntax a[i,j,...] is converted by the compiler to getindex(a, i, j, ...).
Store the given value at the given key or index within a collection. The syntax a[i,j,...] = x is converted by the compiler to setindex!(a, x, i, j, ...).
Fully implemented by: Array, DArray, BitArray, AbstractArray, SubArray, ObjectIdDict, Dict, WeakKeyDict, String.
Partially implemented by: Range, Range1, Tuple.
Dict is the standard associative collection. Its implementation uses the hash(x) as the hashing function for the key, and isequal(x,y) to determine equality. Define these two functions for custom types to override how they are stored in a hash table.
ObjectIdDict is a special hash table where the keys are always object identities. WeakKeyDict is a hash table implementation where the keys are weak references to objects, and thus may be garbage collected even when referenced in a hash table.
Dicts can be created using a literal syntax: {"A"=>1, "B"=>2}. Use of curly brackets will create a Dict of type Dict{Any,Any}. Use of square brackets will attempt to infer type information from the keys and values (i.e. ["A"=>1, "B"=>2] creates a Dict{ASCIIString, Int64}). To explicitly specify types use the syntax: (KeyType=>ValueType)[...]. For example, (ASCIIString=>Int32)["A"=>1, "B"=>2].
As with arrays, Dicts may be created with comprehensions. For example, {i => f(i) for i = 1:10}.
Dict{K,V}() constructs a hashtable with keys of type K and values of type V. The literal syntax is {"A"=>1, "B"=>2} for a Dict{Any,Any}, or ["A"=>1, "B"=>2] for a Dict of inferred type.
Determine whether a collection has a mapping for a given key.
Return the value stored for the given key, or the given default value if no mapping for the key is present.
Return the key matching argument key if one exists in collection, otherwise return default.
Delete the mapping for the given key in a collection, and return the colection.
Delete and return the mapping for key if it exists in collection, otherwise return default, or throw an error if default is not specified.
Return an iterator over all keys in a collection. collect(keys(d)) returns an array of keys.
Return an iterator over all values in a collection. collect(values(d)) returns an array of values.
Construct a merged collection from the given collections.
Update collection with pairs from the other collections
Suggest that collection s reserve capacity for at least n elements. This can improve performance.
Fully implemented by: ObjectIdDict, Dict, WeakKeyDict.
Partially implemented by: IntSet, Set, EnvHash, Array, BitArray.
Add an element to a set-like collection.
Construct a Set with the given elements. Should be used instead of IntSet for sparse integer sets, or for sets of arbitrary objects.
Construct a sorted set of the given integers. Implemented as a bit string, and therefore designed for dense integer sets. If the set will be sparse (for example holding a single very large integer), use Set instead.
Construct the union of two or more sets. Maintains order with arrays.
Union each element of iterable into set s in-place.
Construct the intersection of two or more sets. Maintains order and multiplicity of the first argument for arrays and ranges.
Construct the set of elements in s1 but not s2. Maintains order with arrays.
Remove each element of iterable from set s in-place.
Construct the symmetric difference of elements in the passed in sets or arrays. Maintains order with arrays.
IntSet s is destructively modified to toggle the inclusion of integer n.
For each element in itr, destructively toggle its inclusion in set s.
Construct the symmetric difference of IntSets s1 and s2, storing the result in s1.
Returns the set-complement of IntSet s.
Mutates IntSet s into its set-complement.
Intersects IntSets s1 and s2 and overwrites the set s1 with the result. If needed, s1 will be expanded to the size of s2.
True if A ⊆ S (A is a subset of or equal to S)
Fully implemented by: IntSet, Set.
Partially implemented by: Array.
Insert an item at the end of a collection.
Remove the last item in a collection and return it.
Insert an item at the beginning of a collection.
Remove the first item in a collection.
Insert an item at the given index.
Remove the item at the given index, and return the removed item. Subsequent items are shifted down to fill the resulting gap. If specified, replacement values from an ordered collection will be spliced in place of the removed item.
Remove items in the specified index range, and return a collection containing the removed items. Subsequent items are shifted down to fill the resulting gap. If specified, replacement values from an ordered collection will be spliced in place of the removed items.
Resize collection to contain n elements.
Add the elements of items to the end of a collection. append!([1],[2,3]) => [1,2,3]
Insert the elements of items to the beginning of a collection. prepend!([3],[1,2]) => [1,2,3]
Fully implemented by: Vector (aka 1-d Array), BitVector (aka 1-d BitArray).
The number of characters in string s.
The number of bytes in string s.
Concatenate strings.
Example: "Hello " * "world" == "Hello world"
Repeat string s n times.
Example: "Julia "^3 == "Julia Julia Julia "
Create a string from any values using the print function.
Create a string from any value using the show function.
Create a string from the address of a C (0-terminated) string. A copy is made; the ptr can be safely freed.
Convert a string to a contiguous byte array representation appropriate for passing it to C functions.
Create an ASCII string from a byte array.
Convert a string to a contiguous ASCII string (all characters must be valid ASCII characters).
Create a UTF-8 string from a byte array.
Convert a string to a contiguous UTF-8 string (all characters must be valid UTF-8 characters).
Returns true if the string or byte vector is valid ASCII, false otherwise.
Returns true if the string or byte vector is valid UTF-8, false otherwise.
Returns true if the given char or integer is a valid Unicode code point.
Test whether a string contains a match of the given regular expression.
Search for the first match of the regular expression r in s and return a RegexMatch object containing the match, or nothing if the match failed. The matching substring can be retrieved by accessing m.match and the captured sequences can be retrieved by accessing m.captures
Search for all matches of a the regular expression r in s and return a iterator over the matches. If overlap is true, the matching sequences are allowed to overlap indices in the original string, otherwise they must be from distinct character ranges.
Return a vector of the matching substrings from eachmatch.
Make a string at least n characters long by padding on the left with copies of p.
Make a string at least n characters long by padding on the right with copies of p.
Search for the first occurance of the given characters within the given string. The second argument may be a single character, a vector or a set of characters, a string, or a regular expression (though regular expressions are only allowed on contiguous strings, such as ASCII or UTF-8 strings). The third argument optionally specifies a starting index. The return value is a range of indexes where the matching sequence is found, such that s[search(s,x)] == x:
search(string, “substring”) = start:end such that string[start:end] == "substring", or 0:-1 if unmatched.
search(string, ‘c’) = index such that string[index] == 'c', or 0 if unmatched.
Similar to search, but returning the last occurance of the given characters within the given string, searching in reverse from start.
Similar to search, but return only the start index at which the substring is found, or 0 if it is not.
Similar to rsearch, but return only the start index at which the substring is found, or 0 if it is not.
Determine whether the second argument is a substring of the first.
Search for the given pattern pat, and replace each occurrence with r. If n is provided, replace at most n occurrences. As with search, the second argument may be a single character, a vector or a set of characters, a string, or a regular expression. If r is a function, each occurrence is replaced with r(s) where s is the matched substring.
Return an array of strings by splitting the given string on occurrences of the given character delimiters, which may be specified in any of the formats allowed by search‘s second argument (i.e. a single character, collection of characters, string, or regular expression). If chars is omitted, it defaults to the set of all space characters, and include_empty is taken to be false. The last two arguments are also optional: they are are a maximum size for the result and a flag determining whether empty fields should be included in the result.
Similar to split, but starting from the end of the string.
Return string with any leading and trailing whitespace removed. If a string chars is provided, instead remove characters contained in that string.
Return string with any leading whitespace removed. If a string chars is provided, instead remove characters contained in that string.
Return string with any trailing whitespace removed. If a string chars is provided, instead remove characters contained in that string.
Returns true if string starts with prefix.
Returns true if string ends with suffix.
Returns string with all characters converted to uppercase.
Returns string with all characters converted to lowercase.
Returns string with the first character converted to uppercase.
Returns string with the first character converted to lowercase.
Join an array of strings into a single string, inserting the given delimiter between adjacent strings.
Remove the last character from a string
Remove a trailing newline from a string
Convert a byte index to a character index
Convert a character index to a byte index
Tells whether index i is valid for the given string
Get the next valid string index after i. Returns endof(str)+1 at the end of the string.
Get the previous valid string index before i. Returns 0 at the beginning of the string.
Create a random ASCII string of length len, consisting of upper- and lower-case letters and the digits 0-9
Gives the number of columns needed to print a character.
Gives the number of columns needed to print a string.
Tests whether a character is alphanumeric, or whether this is true for all elements of a string.
Tests whether a character is alphabetic, or whether this is true for all elements of a string.
Tests whether a character belongs to the ASCII character set, or whether this is true for all elements of a string.
Tests whether a character is a tab or space, or whether this is true for all elements of a string.
Tests whether a character is a control character, or whether this is true for all elements of a string.
Tests whether a character is a numeric digit (0-9), or whether this is true for all elements of a string.
Tests whether a character is printable, and not a space, or whether this is true for all elements of a string.
Tests whether a character is a lowercase letter, or whether this is true for all elements of a string.
Tests whether a character is printable, including space, or whether this is true for all elements of a string.
Tests whether a character is printable, and not a space or alphanumeric, or whether this is true for all elements of a string.
Tests whether a character is any whitespace character, or whether this is true for all elements of a string.
Tests whether a character is an uppercase letter, or whether this is true for all elements of a string.
Tests whether a character is a valid hexadecimal digit, or whether this is true for all elements of a string.
Convert a string to a Symbol.
General escaping of traditional C and Unicode escape sequences. See print_escaped() for more general escaping.
General unescaping of traditional C and Unicode escape sequences. Reverse of escape_string(). See also print_unescaped().
Global variable referring to the standard out stream.
Global variable referring to the standard error stream.
Global variable referring to the standard input stream.
Open a file in a mode specified by five boolean arguments. The default is to open files for reading only. Returns a stream for accessing the file.
Alternate syntax for open, where a string-based mode specifier is used instead of the five booleans. The values of mode correspond to those from fopen(3) or Perl open, and are equivalent to setting the following boolean groups:
r | read |
r+ | read, write |
w | write, create, truncate |
w+ | read, write, create, truncate |
a | write, create, append |
a+ | read, write, create, append |
Apply the function f to the result of open(args...) and close the resulting file descriptor upon completion.
Example: open(readall, "file.txt")
Create an in-memory I/O stream.
Create a fixed size IOBuffer. The buffer will not grow dynamically.
Create a read-only IOBuffer on the data underlying the given string
Create an IOBuffer, which may optionally operate on a pre-existing array. If the readable/writable arguments are given, they restrict whether or not the buffer may be read from or written to respectively. By default the buffer is readable but not writable. The last argument optionally specifies a size beyond which the buffer may not be grown.
Obtain the contents of an IOBuffer as an array, without copying.
Obtain the contents of an IOBuffer as a string, without copying.
Create an IOStream object from an integer file descriptor. If own is true, closing this object will close the underlying descriptor. By default, an IOStream is closed when it is garbage collected. name allows you to associate the descriptor with a named file.
Commit all currently buffered writes to the given stream.
Flushes the C stdout and stderr streams (which may have been written to by external C code).
Close an I/O stream. Performs a flush first.
Write the canonical binary representation of a value to the given stream.
Read a value of the given type from a stream, in canonical binary representation.
Read a series of values of the given type from a stream, in canonical binary representation. dims is either a tuple or a series of integer arguments specifying the size of Array to return.
Read at most nb bytes from the stream into b, returning the number of bytes read (increasing the size of b as needed).
Read at most nb bytes from the stream, returning a Vector{Uint8} of the bytes read.
Get the current position of a stream.
Seek a stream to the given position.
Seek a stream to its beginning.
Seek a stream to its end.
Seek a stream relative to the current position.
Tests whether an I/O stream is at end-of-file. If the stream is not yet exhausted, this function will block to wait for more data if necessary, and then return false. Therefore it is always safe to read one byte after seeing eof return false. eof will return false as long as buffered data is still available, even if the remote end of a connection is closed.
Determine whether a stream is read-only.
Determine whether a stream is open (i.e. has not been closed yet). If the connection has been closed remotely (in case of e.g. a socket), isopen will return false even though buffered data may still be available. Use eof to check if necessary.
Converts the endianness of a value from Network byte order (big-endian) to that used by the Host.
Converts the endianness of a value from that used by the Host to Network byte order (big-endian).
Converts the endianness of a value from Little-endian to that used by the Host.
Converts the endianness of a value from that used by the Host to Little-endian.
The 32-bit byte-order-mark indicates the native byte order of the host machine. Little-endian machines will contain the value 0x04030201. Big-endian machines will contain the value 0x01020304.
Write an arbitrary value to a stream in an opaque format, such that it can be read back by deserialize. The read-back value will be as identical as possible to the original. In general, this process will not work if the reading and writing are done by different versions of Julia, or an instance of Julia with a different system image.
Read a value written by serialize.
General escaping of traditional C and Unicode escape sequences, plus any characters in esc are also escaped (with a backslash).
General unescaping of traditional C and Unicode escape sequences. Reverse of print_escaped().
Print elements of items to io with delim between them. If last is specified, it is used as the final delimiter instead of delim.
Print the shortest possible representation of number x as a floating point number, ensuring that it would parse to the exact same number.
Returns the file descriptor backing the stream or file. Note that this function only applies to synchronous File‘s and IOStream‘s not to any of the asynchronous streams.
Create a pipe to which all C and Julia level STDOUT output will be redirected. Returns a tuple (rd,wr) representing the pipe ends. Data written to STDOUT may now be read from the rd end of the pipe. The wr end is given for convenience in case the old STDOUT object was cached by the user and needs to be replaced elsewhere.
Replace STDOUT by stream for all C and julia level output to STDOUT. Note that stream must be a TTY, a Pipe or a TcpSocket.
Like redirect_stdout, but for STDERR
Like redirect_stdout, but for STDIN. Note that the order of the return tuple is still (rd,wr), i.e. data to be read from STDIN, may be written to wr.
Read the entirety of x as a string but remove trailing newlines. Equivalent to chomp(readall(x)).
Returns the files and directories in the directory dir (or the current working directory if not given).
Resize the file or buffer given by the first argument to exactly n bytes, filling previously unallocated space with ‘0’ if the file or buffer is grown
Advance the stream until before the first character for which predicate returns false. For example skipchars(stream, isspace) will skip all whitespace. If keyword argument linecomment is specified, characters from that character through the end of a line will also be skipped.
Read io until the end of the stream/file and count the number of non-empty lines. To specify a file pass the filename as the first argument. EOL markers other than ‘n’ are supported by passing them as the second argument.
An IOBuffer that allows reading and performs writes by appending. Seeking and truncating are not supported. See IOBuffer for the available constructors.
Create a PipeBuffer to operate on a data vector, optionally specifying a size beyond which the underlying Array may not be grown.
Read all available data on the stream, blocking the task only if no data is available.
Returns a structure whose fields contain information about the file. The fields of the structure are:
size | The size (in bytes) of the file |
device | ID of the device that contains the file |
inode | The inode number of the file |
mode | The protection mode of the file |
nlink | The number of hard links to the file |
uid | The user id of the owner of the file |
gid | The group id of the file owner |
rdev | If this file refers to a device, the ID of the device it refers to |
blksize | The file-system preffered block size for the file |
blocks | The number of such blocks allocated |
mtime | Unix timestamp of when the file was last modified |
ctime | Unix timestamp of when the file was created |
Like stat, but for symbolic links gets the info for the link itself rather than the file it refers to. This function must be called on a file path rather than a file object or a file descriptor.
Equivalent to stat(file).ctime
Equivalent to stat(file).mtime
Equivalent to stat(file).mode
Equivalent to stat(file).size
Gets the permissions of the owner of the file as a bitfield of
01 | Execute Permission |
02 | Write Permission |
04 | Read Permission |
For allowed arguments, see the stat method.
Like uperm but gets the permissions of the group owning the file
Like uperm but gets the permissions for people who neither own the file nor are a member of the group owning the file
Copy a file from src to dest.
Download a file from the given url, optionally renaming it to the given local file name. Note that this function relies on the availability of external tools such as curl, wget or fetch to download the file and is provided for convenience. For production use or situations in which more options are need, please use a package that provides the desired functionality instead.
Move a file from src to dst.
Delete the file at the given path. Note that this does not work on directories.
Update the last-modified timestamp on a file to the current time.
Connect to the host host on port port
Connect to the Named Pipe/Domain Socket at path
Listen on port on the address specified by addr. By default this listens on localhost only. To listen on all interfaces pass, IPv4(0) or IPv6(0) as appropriate.
Listens on/Creates a Named Pipe/Domain Socket
Gets the IP address of the host (may have to do a DNS lookup)
Parse a string specifying an IPv4 or IPv6 ip address.
Returns IPv4 object from ip address formatted as Integer
Returns IPv6 object from ip address formatted as Integer
Returns the number of bytes available for reading before a read from this stream or buffer will block.
Accepts a connection on the given server and returns a connection to the client. An uninitialized client stream may be provided, in which case it will be used instead of creating a new stream.
Create a TcpServer on any port, using hint as a starting point. Returns a tuple of the actual port that the server was created on and the server itself.
Watch file or directory s and run callback cb when s is modified. The poll parameter specifies whether to use file system event monitoring or polling. The callback function cb should accept 3 arguments: (filename, events, status) where filename is the name of file that was modified, events is an object with boolean fields changed and renamed when using file system event monitoring, or readable and writable when using polling, and status is always 0. Pass false for cb to not use a callback function.
Poll a file descriptor fd for changes in the read or write availability and with a timeout given by the second argument. If the timeout is not needed, use wait(fd) instead. The keyword arguments determine which of read and/or write status should be monitored and at least one of them needs to be set to true. The returned value is an object with boolean fields readable, writable, and timedout, giving the result of the polling.
Monitor a file for changes by polling every interval_seconds seconds for seconds seconds. A return value of true indicates the file changed, a return value of false indicates a timeout.
Write an informative text representation of a value to the current output stream. New types should overload show(io, x) where the first argument is a stream. The representation used by show generally includes Julia-specific formatting and type information.
Show a more compact representation of a value. This is used for printing array elements. If a new type has a different compact representation, it should overload showcompact(io, x) where the first argument is a stream.
Similar to show, except shows all elements of arrays.
Return a string giving a brief description of a value. By default returns string(typeof(x)). For arrays, returns strings like “2x2 Float64 Array”.
Write (to the default output stream) a canonical (un-decorated) text representation of a value if there is one, otherwise call show. The representation used by print includes minimal formatting and tries to avoid Julia-specific details.
Print strings in a color specified as a symbol, for example :red or :blue.
Display an informational message.
Display a warning.
Print arg(s) using C printf() style format specification string. Optionally, an IOStream may be passed as the first argument to redirect output.
Return @printf formatted output as string.
Call the given function with an I/O stream and the supplied extra arguments. Everything written to this I/O stream is returned as a string.
Show a descriptive representation of an exception object.
Show all user-visible structure of a value.
Show all structure of a value, including all fields of objects.
Read the entire contents of an I/O stream as a string.
Read a single line of text, including a trailing newline character (if one is reached before the end of the input).
Read a string, up to and including the given delimiter byte.
Read all lines as an array.
Create an iterable object that will yield each line from a stream.
Read a matrix from the source where each line gives one row, with elements separated by the given delimeter. The source can be a text file, stream or byte array. Memory mapped filed can be used by passing the byte array representation of the mapped segment as source.
If has_header is true the first row of data would be read as headers and the tuple (data_cells, header_cells) is returned instead of only data_cells.
If use_mmap is true the file specified by source is memory mapped for potential speedups.
If ignore_invalid_chars is true bytes in source with invalid character encoding will be ignored. Otherwise an error is thrown indicating the offending character position.
If all data is numeric, the result will be a numeric array. If some elements cannot be parsed as numbers, a cell array of numbers and strings is returned.
Read a matrix from the source with a given element type. If T is a numeric type, the result is an array of that type, with any non-numeric elements as NaN for floating-point types, or zero. Other useful values of T include ASCIIString, String, and Any.
Write an array to a text file using the given delimeter (defaults to comma).
Equivalent to readdlm with delim set to comma.
Equivalent to writedlm with delim set to comma.
Returns a new write-only I/O stream, which converts any bytes written to it into base64-encoded ASCII bytes written to ostream. Calling close on the Base64Pipe stream is necessary to complete the encoding (but does not close ostream).
Given a write-like function writefunc, which takes an I/O stream as its first argument, base64(writefunc, args...) calls writefunc to write args... to a base64-encoded string, and returns the string. base64(args...) is equivalent to base64(write, args...): it converts its arguments into bytes using the standard write functions and returns the base64-encoded string.
Just as text output is performed by print and user-defined types can indicate their textual representation by overloading show, Julia provides a standardized mechanism for rich multimedia output (such as images, formatted text, or even audio and video), consisting of three parts:
The base Julia runtime provides only plain-text display, but richer displays may be enabled by loading external modules or by using graphical Julia environments (such as the IPython-based IJulia notebook).
Display x using the topmost applicable display in the display stack, typically using the richest supported multimedia output for x, with plain-text STDOUT output as a fallback. The display(d, x) variant attempts to display x on the given display d only, throwing a MethodError if d cannot display objects of this type.
There are also two variants with a mime argument (a MIME type string, such as "image/png"), which attempt to display x using the requesed MIME type only, throwing a MethodError if this type is not supported by either the display(s) or by x. With these variants, one can also supply the “raw” data in the requested MIME type by passing x::String (for MIME types with text-based storage, such as text/html or application/postscript) or x::Vector{Uint8} (for binary MIME types).
By default, the redisplay functions simply call display. However, some display backends may override redisplay to modify an existing display of x (if any). Using redisplay is also a hint to the backend that x may be redisplayed several times, and the backend may choose to defer the display until (for example) the next interactive prompt.
Returns a boolean value indicating whether the given mime type (string) is displayable by any of the displays in the current display stack, or specifically by the display d in the second variant.
The display functions ultimately call writemime in order to write an object x as a given mime type to a given I/O stream (usually a memory buffer), if possible. In order to provide a rich multimedia representation of a user-defined type T, it is only necessary to define a new writemime method for T, via: writemime(stream, ::MIME"mime", x::T) = ..., where mime is a MIME-type string and the function body calls write (or similar) to write that representation of x to stream. (Note that the MIME"" notation only supports literal strings; to construct MIME types in a more flexible manner use MIME{symbol("")}.)
For example, if you define a MyImage type and know how to write it to a PNG file, you could define a function writemime(stream, ::MIME"image/png", x::MyImage) = ...` to allow your images to be displayed on any PNG-capable Display (such as IJulia). As usual, be sure to import Base.writemime in order to add new methods to the built-in Julia function writemime.
Technically, the MIME"mime" macro defines a singleton type for the given mime string, which allows us to exploit Julia’s dispatch mechanisms in determining how to display objects of any given type.
Returns a boolean value indicating whether or not the object x can be written as the given mime type. (By default, this is determined automatically by the existence of the corresponding writemime function for typeof(x).)
Returns a String or Vector{Uint8} containing the representation of x in the requested mime type, as written by writemime (throwing a MethodError if no appropriate writemime is available). A String is returned for MIME types with textual representations (such as "text/html" or "application/postscript"), whereas binary data is returned as Vector{Uint8}. (The function istext(mime) returns whether or not Julia treats a given mime type as text.)
As a special case, if x is a String (for textual MIME types) or a Vector{Uint8} (for binary MIME types), the reprmime function assumes that x is already in the requested mime format and simply returns x.
Returns a String containing the representation of x in the requested mime type. This is similar to reprmime except that binary data is base64-encoded as an ASCII string.
As mentioned above, one can also define new display backends. For example, a module that can display PNG images in a window can register this capability with Julia, so that calling display(x) on types with PNG representations will automatically display the image using the module’s window.
In order to define a new display backend, one should first create a subtype D of the abstract class Display. Then, for each MIME type (mime string) that can be displayed on D, one should define a function display(d::D, ::MIME"mime", x) = ... that displays x as that MIME type, usually by calling reprmime(mime, x). A MethodError should be thrown if x cannot be displayed as that MIME type; this is automatic if one calls reprmime. Finally, one should define a function display(d::D, x) that queries mimewritable(mime, x) for the mime types supported by D and displays the “best” one; a MethodError should be thrown if no supported MIME types are found for x. Similarly, some subtypes may wish to override redisplay(d::D, ...). (Again, one should import Base.display to add new methods to display.) The return values of these functions are up to the implementation (since in some cases it may be useful to return a display “handle” of some type). The display functions for D can then be called directly, but they can also be invoked automatically from display(x) simply by pushing a new display onto the display-backend stack with:
Pushes a new display d on top of the global display-backend stack. Calling display(x) or display(mime, x) will display x on the topmost compatible backend in the stack (i.e., the topmost backend that does not throw a MethodError).
Pop the topmost backend off of the display-backend stack, or the topmost copy of d in the second variant.
Returns a TextDisplay <: Display, which can display any object as the text/plain MIME type (only), writing the text representation to the given I/O stream. (The text representation is the same as the way an object is printed in the Julia REPL.)
Determine whether a MIME type is text data.
Create an Array whose values are linked to a file, using memory-mapping. This provides a convenient way of working with data too large to fit in the computer’s memory.
The type determines how the bytes of the array are interpreted. Note that the file must be stored in binary format, and no format conversions are possible (this is a limitation of operating systems, not Julia).
dims is a tuple specifying the size of the array.
The file is passed via the stream argument. When you initialize the stream, use "r" for a “read-only” array, and "w+" to create a new array used to write values to disk.
Optionally, you can specify an offset (in bytes) if, for example, you want to skip over a header in the file. The default value for the offset is the current stream position.
Example:
# Create a file for mmapping
# (you could alternatively use mmap_array to do this step, too)
A = rand(1:20, 5, 30)
s = open("/tmp/mmap.bin", "w+")
# We'll write the dimensions of the array as the first two Ints in the file
write(s, size(A,1))
write(s, size(A,2))
# Now write the data
write(s, A)
close(s)
# Test by reading it back in
s = open("/tmp/mmap.bin") # default is read-only
m = read(s, Int)
n = read(s, Int)
A2 = mmap_array(Int, (m,n), s)
This would create a m-by-n Matrix{Int}, linked to the file associated with stream s.
A more portable file would need to encode the word size—32 bit or 64 bit—and endianness information in the header. In practice, consider encoding binary data using standard formats like HDF5 (which can be used with memory-mapping).
Create a BitArray whose values are linked to a file, using memory-mapping; it has the same purpose, works in the same way, and has the same arguments, as mmap_array(), but the byte representation is different. The type parameter is optional, and must be Bool if given.
Example: B = mmap_bitarray((25,30000), s)
This would create a 25-by-30000 BitArray, linked to the file associated with stream s.
Forces synchronization between the in-memory version of a memory-mapped Array or BitArray and the on-disk version.
Forces synchronization of the mmap’d memory region from ptr to ptr+len. Flags defaults to MS_SYNC, but can be a combination of MS_ASYNC, MS_SYNC, or MS_INVALIDATE. See your platform man page for specifics. The flags argument is not valid on Windows.
You may not need to call msync, because synchronization is performed at intervals automatically by the operating system. However, you can call this directly if, for example, you are concerned about losing the result of a long-running calculation.
Enum constant for msync. See your platform man page for details. (not available on Windows).
Enum constant for msync. See your platform man page for details. (not available on Windows).
Enum constant for msync. See your platform man page for details. (not available on Windows).
Low-level interface to the mmap system call. See the man page.
Low-level interface for unmapping memory (see the man page). With mmap_array you do not need to call this directly; the memory is unmapped for you when the array goes out of scope.
Bool Int8 Uint8 Int16 Uint16 Int32 Uint32 Int64 Uint64 Int128 Uint128 Float16 Float32 Float64 Complex64 Complex128
Unary minus operator.
Binary addition operator.
Binary subtraction operator.
Binary multiplication operator.
Binary left-division operator.
Binary right-division operator.
Binary exponentiation operator.
Element-wise binary addition operator.
Element-wise binary subtraction operator.
Element-wise binary multiplication operator.
Element-wise binary left division operator.
Element-wise binary right division operator.
Element-wise binary exponentiation operator.
Compute a/b, truncating to an integer
Largest integer less than or equal to a/b
Modulus after division, returning in the range [0,m)
Remainder after division
Compute x/y and x%y at the same time
Remainder after division. The operator form of rem.
Modulus after division, returning in the range (0,m]
Remainder after division, returning in the range (0,m]
Rational division
Approximate the number x as a rational fraction
Numerator of the rational representation of x
Denominator of the rational representation of x
Left shift operator.
Right shift operator.
Unsigned right shift operator.
Range operator. a:b constructs a range from a to b with a step size of 1, and a:s:b is similar but uses a step size of s. These syntaxes call the function colon. The colon is also used in indexing to select whole dimensions.
Called by : syntax for constructing ranges.
Numeric equality operator. Compares numbers and number-like values (e.g. arrays) by numeric value. True for numbers of different types that represent the same value (e.g. 2 and 2.0). Follows IEEE semantics for floating-point numbers. New numeric types should implement this function for two arguments of the new type.
Not-equals comparison operator. Always gives the opposite answer as ==. New types should generally not implement this, and rely on the fallback definition !=(x,y) = !(x==y) instead.
Equivalent to !is(x, y)
Less-than comparison operator. New numeric types should implement this function for two arguments of the new type.
Less-than-or-equals comparison operator.
Greater-than comparison operator. Generally, new types should implement < instead of this function, and rely on the fallback definition >(x,y) = y<x.
Greater-than-or-equals comparison operator.
Element-wise equality comparison operator.
Element-wise not-equals comparison operator.
Element-wise less-than comparison operator.
Element-wise less-than-or-equals comparison operator.
Element-wise greater-than comparison operator.
Element-wise greater-than-or-equals comparison operator.
Return -1, 0, or 1 depending on whether x<y, x==y, or x>y, respectively.
Bitwise not
Bitwise and
Bitwise or
Bitwise exclusive or
Boolean not
Boolean and
Boolean or
Matrix operator A \ B^{H}
Matrix operator A \ B^{T}
Matrix operator A B
Matrix operator A B^{H}
Matrix operator A B^{T}
Matrix operator A / B^{H}
Matrix operator A / B^{T}
Matrix operator A^{H} \ B
Matrix operator A^{H} \ B^{H}
Matrix operator A^{H} B
Matrix operator A^{H} B^{H}
Matrix operator A^{H} / B
Matrix operator A^{H} / B^{H}
Matrix operator A^{T} \ B
Matrix operator A^{T} \ B^{T}
Matrix operator A^{T} B
Matrix operator A^{T} B^{T}
Matrix operator A^{T} / B
Matrix operator A^{T} / B^{T}
Inexact equality comparison - behaves slightly different depending on types of input args:
For default tolerance arguments, maxeps = max(eps(abs(x)), eps(abs(y))).
Compute sine of x, where x is in radians
Compute cosine of x, where x is in radians
Compute tangent of x, where x is in radians
Compute sine of x, where x is in degrees
Compute cosine of x, where x is in degrees
Compute tangent of x, where x is in degrees
Compute \(\sin(\pi x)\) more accurately than sin(pi*x), especially for large x.
Compute \(\cos(\pi x)\) more accurately than cos(pi*x), especially for large x.
Compute hyperbolic sine of x
Compute hyperbolic cosine of x
Compute hyperbolic tangent of x
Compute the inverse sine of x, where the output is in radians
Compute the inverse cosine of x, where the output is in radians
Compute the inverse tangent of x, where the output is in radians
Compute the inverse tangent of y/x, using the signs of both x and y to determine the quadrant of the return value.
Compute the inverse sine of x, where the output is in degrees
Compute the inverse cosine of x, where the output is in degrees
Compute the inverse tangent of x, where the output is in degrees
Compute the secant of x, where x is in radians
Compute the cosecant of x, where x is in radians
Compute the cotangent of x, where x is in radians
Compute the secant of x, where x is in degrees
Compute the cosecant of x, where x is in degrees
Compute the cotangent of x, where x is in degrees
Compute the inverse secant of x, where the output is in radians
Compute the inverse cosecant of x, where the output is in radians
Compute the inverse cotangent of x, where the output is in radians
Compute the inverse secant of x, where the output is in degrees
Compute the inverse cosecant of x, where the output is in degrees
Compute the inverse cotangent of x, where the output is in degrees
Compute the hyperbolic secant of x
Compute the hyperbolic cosecant of x
Compute the hyperbolic cotangent of x
Compute the inverse hyperbolic sine of x
Compute the inverse hyperbolic cosine of x
Compute the inverse hyperbolic tangent of x
Compute the inverse hyperbolic secant of x
Compute the inverse hyperbolic cosecant of x
Compute the inverse hyperbolic cotangent of x
Compute \(\sin(\pi x) / (\pi x)\) if \(x \neq 0\), and \(1\) if \(x = 0\).
Compute \(\cos(\pi x) / x - \sin(\pi x) / (\pi x^2)\) if \(x \neq 0\), and \(0\) if \(x = 0\). This is the derivative of sinc(x).
Convert x from degrees to radians
Convert x from radians to degrees
Compute the \(\sqrt{x^2+y^2}\) avoiding overflow and underflow
Compute the natural logarithm of x. Throws DomainError for negative Real arguments. Use complex negative arguments instead.
Compute the logarithm of x to base 2. Throws DomainError for negative Real arguments.
Compute the logarithm of x to base 10. Throws DomainError for negative Real arguments.
Accurate natural logarithm of 1+x. Throws DomainError for Real arguments less than -1.
Return a number x such that it has a magnitude in the interval [1/2, 1) or 0, and val = \(x \times 2^{exp}\).
Compute \(e^x\)
Compute \(2^x\)
Compute \(10^x\)
Compute \(x \times 2^n\)
Return a tuple (fpart,ipart) of the fractional and integral parts of a number. Both parts have the same sign as the argument.
Accurately compute \(e^x-1\)
round(x) returns the nearest integral value of the same type as x to x. round(x, digits) rounds to the specified number of digits after the decimal place, or before if negative, e.g., round(pi,2) is 3.14. round(x, digits, base) rounds using a different base, defaulting to 10, e.g., round(pi, 3, 2) is 3.125.
Returns the nearest integral value of the same type as x not less than x. digits and base work as above.
Returns the nearest integral value of the same type as x not greater than x. digits and base work as above.
Returns the nearest integral value of the same type as x not greater in magnitude than x. digits and base work as above.
Returns the nearest integer to x.
Returns the nearest integer not less than x.
Returns the nearest integer not greater than x.
Returns the nearest integer not greater in magnitude than x.
Rounds (in the sense of round) x so that there are digits significant digits, under a base base representation, default 10. E.g., signif(123.456, 2) is 120.0, and signif(357.913, 4, 2) is 352.0.
Return the minimum of the arguments. Operates elementwise over arrays.
Return the maximum of the arguments. Operates elementwise over arrays.
Return x if lo <= x <= hi. If x < lo, return lo. If x > hi, return hi.
Absolute value of x
Squared absolute value of x
Return x such that it has the same sign as y
Return +1 if x is positive, 0 if x == 0, and -1 if x is negative.
Returns 1 if the value of the sign of x is negative, otherwise 0.
Return x with its sign flipped if y is negative. For example abs(x) = flipsign(x,x).
Return \(\sqrt{x}\). Throws DomainError for negative Real arguments. Use complex negative arguments instead.
Integer square root.
Return \(x^{1/3}\)
Compute the error function of x, defined by \(\frac{2}{\sqrt{\pi}} \int_0^x e^{-t^2} dt\) for arbitrary complex x.
Compute the complementary error function of x, defined by \(1 - \operatorname{erf}(x)\).
Compute the scaled complementary error function of x, defined by \(e^{x^2} \operatorname{erfc}(x)\). Note also that \(\operatorname{erfcx}(-ix)\) computes the Faddeeva function \(w(x)\).
Compute the imaginary error function of x, defined by \(-i \operatorname{erf}(ix)\).
Compute the Dawson function (scaled imaginary error function) of x, defined by \(\frac{\sqrt{\pi}}{2} e^{-x^2} \operatorname{erfi}(x)\).
Compute the inverse error function of a real x, defined by \(\operatorname{erf}(\operatorname{erfinv}(x)) = x\).
Compute the inverse error complementary function of a real x, defined by \(\operatorname{erfc}(\operatorname{erfcinv}(x)) = x\).
Return the real part of the complex number z
Return the imaginary part of the complex number z
Return both the real and imaginary parts of the complex number z
Compute the complex conjugate of a complex number z
Compute the phase angle of a complex number z
Return cos(z) + i*sin(z) if z is real. Return (cos(real(z)) + i*sin(real(z)))/exp(imag(z)) if z is complex
Number of ways to choose k out of n items
Factorial of n
Compute factorial(n)/factorial(k)
Compute the prime factorization of an integer n. Returns a dictionary. The keys of the dictionary correspond to the factors, and hence are of the same type as n. The value associated with each key indicates the number of times the factor appears in the factorization.
Example: \(100=2*2*5*5\); then, factor(100) -> [5=>2,2=>2]
Greatest common (positive) divisor (or zero if x and y are both zero).
Least common (non-negative) multiple.
Greatest common (positive) divisor, also returning integer coefficients u and v that solve ux+vy == gcd(x,y)
Test whether n is a power of two
Next power of two not less than n
Previous power of two not greater than n
Next power of a not less than n
Previous power of a not greater than n
Next integer not less than n that can be written as \(\prod k_i^{p_i}\) for integers \(p_1\), \(p_2\), etc.
Previous integer not greater than n that can be written as \(\prod k_i^{p_i}\) for integers \(p_1\), \(p_2\), etc.
Take the inverse of x modulo m: y such that \(xy = 1 \pmod m\)
Compute \(x^p \pmod m\)
Compute the gamma function of x
Compute the logarithm of absolute value of gamma(x)
Compute the logarithmic factorial of x
Compute the digamma function of x (the logarithmic derivative of gamma(x))
Compute the inverse digamma function of x.
Compute the trigamma function of x (the logarithmic second derivative of gamma(x))
Compute the polygamma function of order m of argument x (the (m+1)th derivative of the logarithm of gamma(x))
kth derivative of the Airy function \(\operatorname{Ai}(x)\).
Airy function \(\operatorname{Ai}(x)\).
Airy function derivative \(\operatorname{Ai}'(x)\).
Airy function derivative \(\operatorname{Ai}'(x)\).
Airy function \(\operatorname{Bi}(x)\).
Airy function derivative \(\operatorname{Bi}'(x)\).
Bessel function of the first kind of order 0, \(J_0(x)\).
Bessel function of the first kind of order 1, \(J_1(x)\).
Bessel function of the first kind of order nu, \(J_\nu(x)\).
Bessel function of the second kind of order 0, \(Y_0(x)\).
Bessel function of the second kind of order 1, \(Y_1(x)\).
Bessel function of the second kind of order nu, \(Y_\nu(x)\).
Bessel function of the third kind of order nu, \(H^{(1)}_\nu(x)\).
Bessel function of the third kind of order nu, \(H^{(2)}_\nu(x)\).
Bessel function of the third kind of order nu (Hankel function). k is either 1 or 2, selecting hankelh1 or hankelh2, respectively.
Modified Bessel function of the first kind of order nu, \(I_\nu(x)\).
Modified Bessel function of the second kind of order nu, \(K_\nu(x)\).
Euler integral of the first kind \(\operatorname{B}(x,y) = \Gamma(x)\Gamma(y)/\Gamma(x+y)\).
Natural logarithm of the absolute value of the beta function \(\log(|\operatorname{B}(x,y)|)\).
Dirichlet eta function \(\eta(s) = \sum^\infty_{n=1}(-)^{n-1}/n^{s}\).
Riemann zeta function \(\zeta(s)\).
Hash two integers into a single integer. Useful for constructing hash functions.
Compute the number of digits in number n written in base b.
Convert an integer to a binary string, optionally specifying a number of digits to pad to.
Convert an integer to a hexadecimal string, optionally specifying a number of digits to pad to.
Convert an integer to a decimal string, optionally specifying a number of digits to pad to.
Convert an integer to an octal string, optionally specifying a number of digits to pad to.
Convert an integer to a string in the given base, optionally specifying a number of digits to pad to. The base can be specified as either an integer, or as a Uint8 array of character values to use as digit symbols.
Returns an array of the digits of n in the given base, optionally padded with zeros to a specified size. More significant digits are at higher indexes, such that n == sum([digits[k]*base^(k-1) for k=1:length(digits)]).
A string giving the literal bit representation of a number.
Parse a string as an integer in the given base (default 10), yielding a number of the specified type (default Int).
Parse a string as a decimal floating point number, yielding a number of the specified type.
Convert a number to a maximum precision representation (typically BigInt or BigFloat). See BigFloat for information about some pitfalls with floating-point numbers.
Convert a number or numeric array to boolean
Convert a number or array to the default integer type on your platform. Alternatively, x can be a string, which is parsed as an integer.
Convert a number or array to the default unsigned integer type on your platform. Alternatively, x can be a string, which is parsed as an unsigned integer.
Convert a number or array to integer type. If x is already of integer type it is unchanged, otherwise it converts it to the default integer type on your platform.
Convert a number to a signed integer
Convert a number to an unsigned integer
Convert a number or array to Int8 data type
Convert a number or array to Int16 data type
Convert a number or array to Int32 data type
Convert a number or array to Int64 data type
Convert a number or array to Int128 data type
Convert a number or array to Uint8 data type
Convert a number or array to Uint16 data type
Convert a number or array to Uint32 data type
Convert a number or array to Uint64 data type
Convert a number or array to Uint128 data type
Convert a number or array to Float16 data type
Convert a number or array to Float32 data type
Convert a number or array to Float64 data type
Convert a number or array to Float32 data type, returning true if successful. The result of the conversion is stored in out[1].
Convert a number or array to Float64 data type, returning true if successful. The result of the conversion is stored in out[1].
Convert a number, array, or string to a FloatingPoint data type. For numeric data, the smallest suitable FloatingPoint type is used. For strings, it converts to Float64.
Extract the significand(s) (a.k.a. mantissa), in binary representation, of a floating-point number or array.
For example, significand(15.2)/15.2 == 0.125, and significand(15.2)*8 == 15.2
Get the exponent of a normalized floating-point number.
Convert to r+i*im represented as a Complex64 data type
Convert to r+i*im represented as a Complex128 data type
Convert a number or array to Char data type
Convert real numbers or arrays to complex
Byte-swap an integer
Get a hexadecimal string of the binary representation of a floating point number
Convert a hexadecimal string to the floating point number it represents
Convert an arbitrarily long hexadecimal string to its binary representation. Returns an Array{Uint8, 1}, i.e. an array of bytes.
Convert an array of bytes to its hexadecimal representation. All characters are in lower-case. Returns an ASCIIString.
Get the multiplicative identity element for the type of x (x can also specify the type itself). For matrices, returns an identity matrix of the appropriate size and type.
Get the additive identity element for the type of x (x can also specify the type itself).
The constant pi
The imaginary unit
The constant e
Catalan’s constant
Positive infinity of type Float64
Positive infinity of type Float32
Positive infinity of type Float16
A not-a-number value of type Float64
A not-a-number value of type Float32
A not-a-number value of type Float16
Test whether a floating point number is subnormal
Test whether a number is finite
Test whether a number is infinite
Test whether a floating point number is not a number (NaN)
Returns infinity in the same floating point type as f (or f can by the type itself)
Returns NaN in the same floating point type as f (or f can by the type itself)
Get the next floating point number in lexicographic order
Get the previous floating point number in lexicographic order
Test whether x or all its elements are numerically equal to some integer
Test whether x or all its elements are numerically equal to some real number
Create an arbitrary precision integer. x may be an Int (or anything that can be converted to an Int) or a String. The usual mathematical operators are defined for this type, and results are promoted to a BigInt.
Create an arbitrary precision floating point number. x may be an Integer, a Float64, a String or a BigInt. The usual mathematical operators are defined for this type, and results are promoted to a BigFloat. Note that because floating-point numbers are not exactly-representable in decimal notation, BigFloat(2.1) may not yield what you expect. You may prefer to initialize constants using strings, e.g., BigFloat("2.1").
Get the current floating point rounding mode. Valid modes are RoundNearest, RoundToZero, RoundUp and RoundDown.
Set the floating point rounding mode. See get_rounding for available modes
Change the floating point rounding mode for the duration of f. It is logically equivalent to:
old = get_rounding()
set_rounding(mode)
f()
set_rounding(old)
See get_rounding for available rounding modes.
Number of ones in the binary representation of x.
Example: count_ones(7) -> 3
Number of zeros in the binary representation of x.
Example: count_zeros(int32(2 ^ 16 - 1)) -> 16
Number of zeros leading the binary representation of x.
Example: leading_zeros(int32(1)) -> 31
Number of ones leading the binary representation of x.
Example: leading_ones(int32(2 ^ 32 - 2)) -> 31
Number of zeros trailing the binary representation of x.
Example: trailing_zeros(2) -> 1
Number of ones trailing the binary representation of x.
Example: trailing_ones(3) -> 2
Returns true if x is prime, and false otherwise.
Example: isprime(3) -> true
Returns a collection of the prime numbers <= n.
Returns true if x is odd (that is, not divisible by 2), and false otherwise.
Example: isodd(9) -> false
Returns true is x is even (that is, divisible by 2), and false otherwise.
Example: iseven(1) -> false
The BigFloat type implements arbitrary-precision floating-point aritmetic using the GNU MPFR library.
Get the precision of a floating point number, as defined by the effective number of bits in the mantissa.
Get the precision (in bits) currently used for BigFloat arithmetic.
Set the precision (in bits) to be used to BigFloat arithmetic.
Change the BigFloat arithmetic precision (in bits) for the duration of f. It is logically equivalent to:
old = get_bigfloat_precision()
set_bigfloat_precision(precision)
f()
set_bigfloat_precision(old)
Get the current BigFloat rounding mode. Valid modes are RoundNearest, RoundToZero, RoundUp, RoundDown, RoundFromZero
Set the BigFloat rounding mode. See get_bigfloat_rounding for available modes
Change the BigFloat rounding mode for the duration of f. See get_bigfloat_rounding for available rounding modes; see also with_bigfloat_precision.
Random number generateion in Julia uses the Mersenne Twister library. Julia has a global RNG, which is used by default. Multiple RNGs can be plugged in using the AbstractRNG object, which can then be used to have multiple streams of random numbers. Currently, only MersenneTwister is supported.
Seed the RNG with a seed, which may be an unsigned integer or a vector of unsigned integers. seed can even be a filename, in which case the seed is read from a file. If the argument rng is not provided, the default global RNG is seeded.
Create a MersenneTwister RNG object. Different RNG objects can have their own seeds, which may be useful for generating different streams of random numbers.
Generate a Float64 random number uniformly in [0,1)
Populate the array A with random number generated from the specified RNG.
Generate a random Float64 number or array of the size specified by dims, using the specified RNG object. Currently, MersenneTwister is the only available Random Number Generator (RNG), which may be seeded using srand.
Generate a random Float64 array of the size specified by dims
Generate a random integer of the given type. Optionally, generate an array of random integers of the given type by specifying dims.
Generate a random integer from the inclusive interval specified by Range1 r (for example, 1:n). Optionally, generate a random integer array.
Generate a random boolean value. Optionally, generate an array of random boolean values.
Fill an array with random boolean values. A may be an Array or a BitArray.
Generate a normally-distributed random number with mean 0 and standard deviation 1. Optionally generate an array of normally-distributed random numbers.
Fill the array A with normally-distributed (mean 0, standard deviation 1) random numbers. Also see the rand function.
Generate a nxn symmetric array of normally-distributed random numbers with mean 0 and standard deviation 1.
Returns the number of dimensions of A
Returns a tuple containing the dimensions of A
Tests whether A or its elements are of type T
Returns the number of elements in A
Counts the number of nonzero values in array A (dense or sparse)
Convert an array to its complex conjugate in-place
Returns the distance in memory (in number of elements) between adjacent elements in dimension k
Returns a tuple of the memory strides in each dimension
Returns a tuple of subscripts into an array with dimensions dims, corresponding to the linear index index
Example i, j, ... = ind2sub(size(A), indmax(A)) provides the indices of the maximum element
The inverse of ind2sub, returns the linear index corresponding to the provided subscripts
Construct an uninitialized dense array. dims may be a tuple or a series of integer arguments.
Construct a 1-d array of the specified type. This is usually called with the syntax Type[]. Element values can be specified using Type[a,b,c,...].
Construct an uninitialized cell array (heterogeneous array). dims can be either a tuple or a series of integer arguments.
Create an array of all zeros of specified type
Create an array of all ones of specified type
Create an array where every element is infinite and of the specified type
Create an array where every element is NaN of the specified type
Create a BitArray with all values set to true
Create a BitArray with all values set to false
Create an array filled with v
Fill array A with value x
Create an array with the same data as the given array, but with different dimensions. An implementation for a particular type of array may choose whether the data is copied or shared.
Create an uninitialized array of the same type as the given array, but with the specified element type and dimensions. The second and third arguments are both optional. The dims argument may be a tuple or a series of integer arguments.
Change the type-interpretation of a block of memory. For example, reinterpret(Float32, uint32(7)) interprets the 4 bytes corresponding to uint32(7) as a Float32. For arrays, this constructs an array with the same binary data as the given array, but with the specified element type.
n-by-n identity matrix
m-by-n identity matrix
Construct a vector of n linearly-spaced elements from start to stop.
Construct a vector of n logarithmically-spaced numbers from 10^start to 10^stop.
All mathematical operations and functions are supported for arrays
Broadcasts the arrays As to a common size by expanding singleton dimensions, and returns an array of the results f(as...) for each position.
Like broadcast, but store the result of broadcast(f, As...) in the dest array. Note that dest is only used to store the result, and does not supply arguments to f unless it is also listed in the As, as in broadcast!(f, A, A, B) to perform A[:] = broadcast(f, A, B).
Returns a function broadcast_f such that broadcast_function(f)(As...) === broadcast(f, As...). Most useful in the form const broadcast_f = broadcast_function(f).
Like broadcast_function, but for broadcast!.
Returns a subset of array A as specified by inds, where each ind may be an Int, a Range, or a Vector.
Returns a SubArray, which stores the input A and inds rather than computing the result immediately. Calling getindex on a SubArray computes the indices on the fly.
Returns the “parent array” of an array view type (e.g., SubArray), or the array itself if it is not a view
From an array view A, returns the corresponding indexes in the parent
Return all the data of A where the index for dimension d equals i. Equivalent to A[:,:,...,i,:,:,...] where i is in position d.
Create a view of the given indexes of array A, dropping dimensions indexed with scalars.
Store values from array X within some subset of A as specified by inds.
Broadcasts the inds arrays to a common size like broadcast, and returns an array of the results A[ks...], where ks goes over the positions in the broadcast.
Broadcasts the X and inds arrays to a common size and stores the value from each position in X at the indices given by the same positions in inds.
Concatenate the input arrays along the specified dimension
Concatenate along dimension 1
Concatenate along dimension 2
Horizontal and vertical concatenation in one call. This function is called for block matrix syntax. The first argument specifies the number of arguments to concatenate in each block row. For example, [a b;c d e] calls hvcat((2,3),a,b,c,d,e).
If the first argument is a single integer n, then all block rows are assumed to have n block columns.
Reverse A in dimension d.
Equivalent to flipdim(A,1).
Equivalent to flipdim(A,2).
Circularly shift the data in an array. The second argument is a vector giving the amount to shift in each dimension.
Return a vector of the linear indexes of the non-zeros in A.
Return a vector of the linear indexes of A where f returns true.
Return a vector of indexes for each dimension giving the locations of the non-zeros in A.
Return a tuple (I, J, V) where I and J are the row and column indexes of the non-zero values in matrix A, and V is a vector of the non-zero values.
Return a vector of the non-zero values in array A.
Return the index of the first non-zero value in A.
Return the index of the first element equal to v in A.
Return the index of the first element that satisfies the given predicate in A.
Find the next index >= i of a non-zero element of A, or 0 if not found.
Find the next index >= i of an element of A satisfying the given predicate, or 0 if not found.
Find the next index >= i of an element of A equal to v (using ==), or 0 if not found.
Permute the dimensions of array A. perm is a vector specifying a permutation of length ndims(A). This is a generalization of transpose for multi-dimensional arrays. Transpose is equivalent to permute(A,[2,1]).
Like permutedims(), except the inverse of the given permutation is applied.
Remove the dimensions specified by dims from array A
Vectorize an array using column-major convention.
Check two array shapes for compatibility, allowing trailing singleton dimensions, and return whichever shape has more dimensions.
Throw an error if the specified indexes are not in bounds for the given array.
Cumulative product along a dimension.
Cumulative sum along a dimension.
Cumulative sum along a dimension, using the Kahan-Babuska-Neumaier compensated summation algorithm for additional accuracy.
Cumulative minimum along a dimension.
Cumulative maximum along a dimension.
Finite difference operator of matrix or vector.
Compute differences along vector F, using h as the spacing between points. The default spacing is one.
Rotate matrix A 180 degrees.
Rotate matrix A left 90 degrees.
Rotate matrix A right 90 degrees.
Reduce 2-argument function f along dimensions of A. dims is a vector specifying the dimensions to reduce, and initial is the initial value to use in the reductions.
The associativity of the reduction is implementation-dependent; if you need a particular associativity, e.g. left-to-right, you should write your own loop.
Transform the given dimensions of array A using function f. f is called on each slice of A of the form A[...,:,...,:,...]. dims is an integer vector specifying where the colons go in this expression. The results are concatenated along the remaining dimensions. For example, if dims is [1,2] and A is 4-dimensional, f is called on A[:,:,i,j] for all i and j.
Returns the sum of all array elements, using the Kahan-Babuska-Neumaier compensated summation algorithm for additional accuracy.
Given a dims tuple of integers (m, n, ...), call f on all combinations of integers in the ranges 1:m, 1:n, etc. Example:
julia> cartesianmap(println, (2,2))
11
21
12
22
Converts a numeric array to a packed boolean array
Converts a packed boolean array to an array of booleans
Performs a bitwise not operation on B. See ~ operator.
Left rotation operator.
Right rotation operator.
Compute the kth lexicographic permutation of a vector.
Construct a random permutation of the given length.
Return the inverse permutation of v.
Returns true if v is a valid permutation.
Permute vector v in-place, according to permutation p. No checking is done to verify that p is a permutation.
To return a new permutation, use v[p]. Note that this is generally faster than permute!(v,p) for large vectors.
Like permute!, but the inverse of the given permutation is applied.
Construct a random cyclic permutation of the given length.
Return a randomly permuted copy of v.
Return a copy of v reversed from start to stop.
Generate all combinations of n elements from a given iterable object. Because the number of combinations can be very large, this function returns an iterator object. Use collect(combinations(a,n)) to get an array of all combinations.
Generate all permutations of a given iterable object. Because the number of permutations can be very large, this function returns an iterator object. Use collect(permutations(a,n)) to get an array of all permutations.
Generate all integer arrays that sum to n. Because the number of partitions can be very large, this function returns an iterator object. Use collect(partitions(n)) to get an array of all partitions. The number of partitions to generete can be efficiently computed using length(partitions(n)).
Generate all arrays of m integers that sum to n. Because the number of partitions can be very large, this function returns an iterator object. Use collect(partitions(n,m)) to get an array of all partitions. The number of partitions to generete can be efficiently computed using length(partitions(n,m)).
Generate all set partitions of the elements of an array, represented as arrays of arrays. Because the number of partitions can be very large, this function returns an iterator object. Use collect(partitions(array)) to get an array of all partitions. The number of partitions to generete can be efficiently computed using length(partitions(array)).
Compute the mean of whole array v, or optionally along the dimensions in region. Note: Julia does not ignore NaN values in the computation. For applications requiring the handling of missing data, the DataArray package is recommended.
Compute the sample standard deviation of a vector or array v, optionally along dimensions in region. The algorithm returns an estimator of the generative distribution’s standard deviation under the assumption that each entry of v is an IID drawn from that generative distribution. This computation is equivalent to calculating sqrt(sum((v - mean(v)).^2) / (length(v) - 1)). Note: Julia does not ignore NaN values in the computation. For applications requiring the handling of missing data, the DataArray package is recommended.
Compute the sample standard deviation of a vector v with known mean m. Note: Julia does not ignore NaN values in the computation.
Compute the sample variance of a vector or array v, optionally along dimensions in region. The algorithm will return an estimator of the generative distribution’s variance under the assumption that each entry of v is an IID drawn from that generative distribution. This computation is equivalent to calculating sum((v - mean(v)).^2) / (length(v) - 1). Note: Julia does not ignore NaN values in the computation. For applications requiring the handling of missing data, the DataArray package is recommended.
Compute the sample variance of a vector v with known mean m. Note: Julia does not ignore NaN values in the computation.
Compute the median of a vector v. If keyword argument checknan is true (the default), an error is raised for data containing NaN values. Note: Julia does not ignore NaN values in the computation. For applications requiring the handling of missing data, the DataArray package is recommended.
Like median, but may overwrite the input vector.
Compute the histogram of v, optionally using approximately n bins. The return values are a range e, which correspond to the edges of the bins, and counts containing the number of elements of v in each bin. Note: Julia does not ignore NaN values in the computation.
Compute the histogram of v using a vector/range e as the edges for the bins. The result will be a vector of length length(e) - 1, such that the element at location i satisfies sum(e[i] .< v .<= e[i+1]). Note: Julia does not ignore NaN values in the computation.
Compute a “2d histogram” of a set of N points specified by N-by-2 matrix M. Arguments e1 and e2 are bins for each dimension, specified either as integer bin counts or vectors of bin edges. The result is a tuple of edge1 (the bin edges used in the first dimension), edge2 (the bin edges used in the second dimension), and counts, a histogram matrix of size (length(edge1)-1, length(edge2)-1). Note: Julia does not ignore NaN values in the computation.
Compute nice bin ranges for the edges of a histogram of v, using approximately n bins. The resulting step sizes will be 1, 2 or 5 multiplied by a power of 10. Note: Julia does not ignore NaN values in the computation.
Compute the midpoints of the bins with edges e. The result is a vector/range of length length(e) - 1. Note: Julia does not ignore NaN values in the computation.
Compute the quantiles of a vector v at a specified set of probability values p. Note: Julia does not ignore NaN values in the computation.
Compute the quantile of a vector v at the probability p. Note: Julia does not ignore NaN values in the computation.
Like quantile, but overwrites the input vector.
Compute the Pearson covariance between two vectors v1 and v2. If called with a single element v, then computes covariance of columns of v. Note: Julia does not ignore NaN values in the computation.
Compute the Pearson correlation between two vectors v1 and v2. If called with a single element v, then computes correlation of columns of v. Note: Julia does not ignore NaN values in the computation.
FFT functions in Julia are largely implemented by calling functions from FFTW
Performs a multidimensional FFT of the array A. The optional dims argument specifies an iterable subset of dimensions (e.g. an integer, range, tuple, or array) to transform along. Most efficient if the size of A along the transformed dimensions is a product of small primes; see nextprod(). See also plan_fft() for even greater efficiency.
A one-dimensional FFT computes the one-dimensional discrete Fourier transform (DFT) as defined by \(\operatorname{DFT}[k] = \sum_{n=1}^{\operatorname{length}(A)} \exp\left(-i\frac{2\pi (n-1)(k-1)}{\operatorname{length}(A)} \right) A[n]\). A multidimensional FFT simply performs this operation along each transformed dimension of A.
Same as fft(), but operates in-place on A, which must be an array of complex floating-point numbers.
Multidimensional inverse FFT.
A one-dimensional backward FFT computes \(\operatorname{BDFT}[k] = \sum_{n=1}^{\operatorname{length}(A)} \exp\left(+i\frac{2\pi (n-1)(k-1)}{\operatorname{length}(A)} \right) A[n]\). A multidimensional backward FFT simply performs this operation along each transformed dimension of A. The inverse FFT computes the same thing divided by the product of the transformed dimensions.
Similar to ifft(), but computes an unnormalized inverse (backward) transform, which must be divided by the product of the sizes of the transformed dimensions in order to obtain the inverse. (This is slightly more efficient than ifft() because it omits a scaling step, which in some applications can be combined with other computational steps elsewhere.)
Pre-plan an optimized FFT along given dimensions (dims) of arrays matching the shape and type of A. (The first two arguments have the same meaning as for fft().) Returns a function plan(A) that computes fft(A, dims) quickly.
The flags argument is a bitwise-or of FFTW planner flags, defaulting to FFTW.ESTIMATE. e.g. passing FFTW.MEASURE or FFTW.PATIENT will instead spend several seconds (or more) benchmarking different possible FFT algorithms and picking the fastest one; see the FFTW manual for more information on planner flags. The optional timelimit argument specifies a rough upper bound on the allowed planning time, in seconds. Passing FFTW.MEASURE or FFTW.PATIENT may cause the input array A to be overwritten with zeros during plan creation.
plan_fft!() is the same as plan_fft() but creates a plan that operates in-place on its argument (which must be an array of complex floating-point numbers). plan_ifft() and so on are similar but produce plans that perform the equivalent of the inverse transforms ifft() and so on.
Same as plan_fft(), but produces a plan that performs inverse transforms ifft().
Same as plan_fft(), but produces a plan that performs an unnormalized backwards transform bfft().
Same as plan_fft(), but operates in-place on A.
Same as plan_ifft(), but operates in-place on A.
Same as plan_bfft(), but operates in-place on A.
Multidimensional FFT of a real array A, exploiting the fact that the transform has conjugate symmetry in order to save roughly half the computational time and storage costs compared with fft(). If A has size (n_1, ..., n_d), the result has size (floor(n_1/2)+1, ..., n_d).
The optional dims argument specifies an iterable subset of one or more dimensions of A to transform, similar to fft(). Instead of (roughly) halving the first dimension of A in the result, the dims[1] dimension is (roughly) halved in the same way.
Inverse of rfft(): for a complex array A, gives the corresponding real array whose FFT yields A in the first half. As for rfft(), dims is an optional subset of dimensions to transform, defaulting to 1:ndims(A).
d is the length of the transformed real array along the dims[1] dimension, which must satisfy d == floor(size(A,dims[1])/2)+1. (This parameter cannot be inferred from size(A) due to the possibility of rounding by the floor function here.)
Similar to irfft() but computes an unnormalized inverse transform (similar to bfft()), which must be divided by the product of the sizes of the transformed dimensions (of the real output array) in order to obtain the inverse transform.
Pre-plan an optimized real-input FFT, similar to plan_fft() except for rfft() instead of fft(). The first two arguments, and the size of the transformed result, are the same as for rfft().
Pre-plan an optimized real-input unnormalized transform, similar to plan_rfft() except for brfft() instead of rfft(). The first two arguments and the size of the transformed result, are the same as for brfft().
Pre-plan an optimized inverse real-input FFT, similar to plan_rfft() except for irfft() and brfft(), respectively. The first three arguments have the same meaning as for irfft().
Performs a multidimensional type-II discrete cosine transform (DCT) of the array A, using the unitary normalization of the DCT. The optional dims argument specifies an iterable subset of dimensions (e.g. an integer, range, tuple, or array) to transform along. Most efficient if the size of A along the transformed dimensions is a product of small primes; see nextprod(). See also plan_dct() for even greater efficiency.
Same as dct!(), except that it operates in-place on A, which must be an array of real or complex floating-point values.
Computes the multidimensional inverse discrete cosine transform (DCT) of the array A (technically, a type-III DCT with the unitary normalization). The optional dims argument specifies an iterable subset of dimensions (e.g. an integer, range, tuple, or array) to transform along. Most efficient if the size of A along the transformed dimensions is a product of small primes; see nextprod(). See also plan_idct() for even greater efficiency.
Pre-plan an optimized discrete cosine transform (DCT), similar to plan_fft() except producing a function that computes dct(). The first two arguments have the same meaning as for dct().
Same as plan_dct(), but operates in-place on A.
Pre-plan an optimized inverse discrete cosine transform (DCT), similar to plan_fft() except producing a function that computes idct(). The first two arguments have the same meaning as for idct().
Same as plan_idct(), but operates in-place on A.
Swap the first and second halves of each dimension of x.
Swap the first and second halves of the given dimension of array x.
Undoes the effect of fftshift.
Apply filter described by vectors a and b to vector x.
Construct vector c such that b = conv(a,c) + r. Equivalent to polynomial division.
Convolution of two vectors. Uses FFT algorithm.
2-D convolution of the matrix A with the 2-D separable kernel generated by the vectors u and v. Uses 2-D FFT algorithm
2-D convolution of the matrix B with the matrix A. Uses 2-D FFT algorithm
Compute the cross-correlation of two vectors.
The following functions are defined within the Base.FFTW module.
Performs a multidimensional real-input/real-output (r2r) transform of type kind of the array A, as defined in the FFTW manual. kind specifies either a discrete cosine transform of various types (FFTW.REDFT00, FFTW.REDFT01, FFTW.REDFT10, or FFTW.REDFT11), a discrete sine transform of various types (FFTW.RODFT00, FFTW.RODFT01, FFTW.RODFT10, or FFTW.RODFT11), a real-input DFT with halfcomplex-format output (FFTW.R2HC and its inverse FFTW.HC2R), or a discrete Hartley transform (FFTW.DHT). The kind argument may be an array or tuple in order to specify different transform types along the different dimensions of A; kind[end] is used for any unspecified dimensions. See the FFTW manual for precise definitions of these transform types, at http://www.fftw.org/doc.
The optional dims argument specifies an iterable subset of dimensions (e.g. an integer, range, tuple, or array) to transform along. kind[i] is then the transform type for dims[i], with kind[end] being used for i > length(kind).
See also plan_r2r() to pre-plan optimized r2r transforms.