Julia’s type system is dynamic. However we can use some advantages of static type systems by using type annotations. This allows  performance optimizations and argument based dispatch. Julia can use native types and user defined types. In Julia type names start with uppercase by convention. If use two or more words name we use CamelCase.

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.

Multiple dispatch

Dispatch technique is the capability of a language to allow creation of polymorphic functions or methods. This is a function that can act on different argument types. In Julia we can create multiple versions of the same function for each argument type or combination of types. These sub-functions are called methods.

A function can have parameters with no type. In this case a dispatch is not possible and the arguments will have type “Any”. The function has only one method. To make dispatch possible we must use a type annotation for one or more arguments using two column symbol “::” that is  named “is an instance of type …”.

In multiple dispatch we use all parameters to identify a method. In Object Oriented programming single dispatch is used. Only one parameter is used to identify a method. The methods are attached to the first argument that is the type. In Java this parameter is invisible (default) and is the current object “this”. In python this parameter is called “self”. In Julia we do not have a similar default parameter.

Before we can use multiple dispatch we have to understand the types. Once we define types we can create multiple methods for these types. Therefore in Julia types are the corner stone of Julia language. Is the foundation Julia  philosophy.

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:

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

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

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
  • Concrete Type

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)

 

See also: Julia Manual:Types