Sage-Code Laboratory
index<--

Julia Types

Julia’s type system is dynamic. However you can use some advantages of static type systems by using type annotations. In Julia you can use both native types and user defined types. This enable performance optimizations and argument based dispatch.

Name Convention

In Julia type names start with uppercase by convention. If use two or more words name you should use CamelCase. This is very important. This convention is like in Java, except that in Java only classes start with capital letters. Native types do not, but in Julia all data types should start with capital letter.

Static typing

This program paradigm is used in traditional languages: Fortran, Ada, C, C++, Pascal, Java, Level. The static typing require all variables to be declared before use. Declaration consist of a variable or argument name and type. If the type of the variable is not native type it has to be defined before the variable can use it.

Dynamic typing

This programming paradigm was created as reaction to rigid typing used in imperative languages. It was considered a "waste of time" to declare and maintain all the variables before use. In dynamic typing we allow the use of a variable before declaration. The type is created at run-time and can be changed in the same scope.

The idea is that a variable or argument do not have a type but only a name. Only values have types. A variable will get the type from the value when is assigned. This is called "type inference" and is used in many new languages: Python, Go and JavaScript. The problem is type inference is not very precise. Sometimes the type that is inferred is not the proper one. So dynamic languages are sometimes slow and inefficient.

Constants

In Julia we use global constants to improve performance and protect them against change. So the constant values are declared using keyword constant to make the program faster. In local scope the constant is not faster than a simple variable.

# Julia example
   constant pi = 3.14
  

Numeric type

Julia uses native numeric types. This is one of the key elements to make fast Julia programs. When we assign a variable the type is automatically assigned using an assumption. If we wish to assign a specific type to a variable we use a "type annotation" this start with "::" symbol.

To write a floating point number we use dot (.) after the number or before the number if the number is <0.

# Float variables
    v = 5.
    x = .05
    y = 5.05
    
  # Integer variables
    i = 2 #use default Int64 type
    z::Int8 = 10 #use type annotation
  

Boolean type

Boolean is a native type having two values: false < true. Boolean variables can be used to create conditions. To create a Boolean variable we can use assign operator with a literal "true" or literal "false", or we can use a logical expression to assign the result to a variable. In next example we use Boolean operators: && for "and", || for "or" and ! for "not". These are the basic operators for Boolean algebra.

# Boolean variables
    locked = false
    closed = true
    is_secure = closed && locked
    not_secure = !closed || !locked
  

Type Declarations

The :: operator can be used to attach type annotations to expressions and variables in programs. There are two primary reasons to do this:

  1. As an assertion to help confirm that your program works the way you expect,
  2. To provide extra type information to the compiler, which can then improve performance

Example:

This function will always return Float64 even if x and y are integers. A function convert() is used to convert the result.

# example of a function that have a result type
function sum(x, y)::Float64
    return x + y
end

Type tree

In Julia types are organized like a tree. This tree is an abstract structure based on inheritance. One type can be derived from another type. The root type is called "Any". There is a specific operator "sub-type of" <: used to specify the parent of a type. If the parent is not specified the parent is Any.

There are two categories of types:

Abstract Type is a node in the tree and can’t be instantiated.

# Examples of abstract types:
abstract type Number end
abstract type Real     <: Number end
abstract type AbstractFloat <: Real end
abstract type Integer  <: Real end
abstract type Signed   <: Integer end
abstract type Unsigned <: Integer end

Primitive Types

Julia define primitive types as concrete types based on a number of bits. These types are predefined. The syntax is simple and can be used to define your own primitive types for a specified number of bits.

primitive type type_name  [<: super_type] number_of_bits end 

In the previous syntax <: super_type can be missing from the declaration That means Any is the super-type.

# Examples of primitive types:
primitive type Float16 <: AbstractFloat 16 end
primitive type Float32 <: AbstractFloat 32 end
primitive type Float64 <: AbstractFloat 64 end

primitive type Bool <: Integer 8 end
primitive type Char 32 end

primitive type Int8    <: Signed   8 end
primitive type UInt8   <: Unsigned 8 end
primitive type Int16   <: Signed   16 end
primitive type UInt16  <: Unsigned 16 end
primitive type Int32   <: Signed   32 end
primitive type UInt32  <: Unsigned 32 end
primitive type Int64   <: Signed   64 end
primitive type UInt64  <: Unsigned 64 end

Composite Types

In other languages we have records or structures. Julia uses keyword "struct" to create composite types. By default a "struct type" is immutable. To create a mutable struct we use keyword "mutable". So it is easy to remember. Immutable structs are more efficient but can’t be changed after initialization. Mutable struct can have it’s member changed.

# Example of composite type
mutable struct Person
   first_name 
   last_name
   age::Integer
end

# We create an instance of Person
person = Person("John","Doe",25)

To create a struct instance we need to use a constructor. The constructor name is the struct name. This is automatically created and is available for use with parameters. We do not use New keyword like in Java and Python. Simple use the name of the struct as constructor.

Parametric Types

This is similar to "generics" in Java. We can define a composite type that have members of a variable type. We do not know the type until is used. This allow us to create meta structures. The use of parametric types is very advanced topic and complex. Be prepared to learn more about it.

# create a parametric type
struct Point{T}
           x::T
           y::T
       end

# create a sub-type of parametric type
julia>point = Point(1.0,2.1)

This will create an object point with coordinates of type: Float64

Point{Float64}(1.0, 2.0)

Array type

In Julia array represents a collection of elements all having the same type. This collection is indexed starting with index 1 until the last element end. We use square brackets [ … ] and elements are separated by comma or semicolon.

# One array of with no elements is represented like this
a = []
# 1-dimensional array literals can be written with comma-separated values.
b = [4, 5, 6] # => 3-element Int64 Array: [4, 5, 6]
b[1] # this is 4
b[end] # this is 6
# 2-dimensional arrays use space-separated values and semicolon-separated rows.
matrix = [1 2; 3 4] # this is 2x2 Int64 Array

Tuple type

Tuples are immutable collection of elements. We use notation (,) to create tuples. The tuple collection is indexed.

tup = (1, 2, 3)
tup[1] # this is 1

Dictionary type

A dictionary is a collection of pairs. Each pair is (key, value).

# Dictionaries store mappings
empty_dict = Dict()
# You can create a dictionary using a literal
filled_dict = Dict("one"=> 1, "two"=> 2, "three"=> 3)

Read next: Expressions