Engee documentation
Notebook

Data types

The Julia language type system is dynamic, but at the same time it contains some of the advantages of static type systems, allowing you to specify that certain values belong to certain types.

There is no separation between object and non-object values: all values in Julia are true objects having a type belonging to one fully connected type graph, all nodes of which are equally first-class as types.

Below is a summary table comparing Engee and MATLAB data types.

| Type (Engee/MATLAB) | Number of bits | Lowest value | Highest value | |--------------------------|----------------|----------------------|-----------------------| | Int8 / int8 | 8 | -2^7 | 2^7 - 1 | | UInt8 / uint8 | 8 | 0 | 2^8 - 1 | | Int16 / int16 | 16 | 16 | -2^{15} | 2^{15} - 1 | | UInt16 / uint16 | 16 | 16 | 0 | 2^{16} - 1 | | Int32 / int32 | 32 | -2^{31} | 2^{31} - 1 | | UInt32 / uint32 | 32 | | 0 | 2^{32} - 1 | | Int64 / int64 | 64 | -2^{63} | 2^{63} - 1 | | UInt64 / uint64 | 64 | 0 | 0 | 2^{64} - 1 | | Int128 / no | 128 | -2^{127} | 2^{127} | 2^{127} - 1 | | UInt128 / no | 128 | 0 | 2^{128} - 1 | | Bool / logical | 8/1 | false(0) | true(1) | | Float16 / no | 16 | Half precision | Half precision | Half precision | | Float32 / single | 32 | Single | Single precision | Single precision | Single precision | | Float64 / double | 64 | Double precision | Double precision | Double precision | (Nifj or Qifj). | (Nifj or Qifj) / fi(i,x,n,m) | (min 8, max 64) / (min 1, max 1024) | Fixed-point number | Fixed-point number | Fixed-point number | Fixed-point number |

Type declarations

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

  1. As a setup to help confirm that your programme works as you expect.
  2. To provide the compiler with additional type information, and in some cases this can improve performance.

When added to an expression that evaluates a value, the :: operator reads "is an instance of".

In [ ]:
(1+2)::Int
Out[0]:
3

When added to a variable in the left-hand side of an assignment, :: declares that the variable always has the specified type, similar to a type declaration in a statically typed language such as C.

In [ ]:
function foo()
    x::Int8 = 100
end
Out[0]:
foo (generic function with 1 method)
In [ ]:
x = foo()
Out[0]:
100
In [ ]:
typeof(x)
Out[0]:
Int64

This feature is useful for preventing performance problems that can occur if one of the assignments to a variable unexpectedly changes its type.

This "declaration" behaviour only occurs in certain contexts and applies to the entire current scope, even before the declaration.

In [ ]:
local x::Int8  # in a local declaration

Type declarations can also be applied to global variables.

In [ ]:
a = 10
Out[0]:
10
In [ ]:
typeof(a)
Out[0]:
Int64
In [ ]:
function foo(y)
        global a = 15   # throws an error when foo is called
        return a + y
end
Out[0]:
foo (generic function with 2 methods)
In [ ]:
foo(10)
Out[0]:
25

Declarations can also be attached to function definitions:

In [ ]:
function sinc(x)::Float64
    if x == 0
        return 1
    end
    return sin(pi*x)/(pi*x)
end

sinc(1.1)
Out[0]:
-0.08942105846213334

Abstract types

Abstract types cannot be created and serve only as nodes in the type graph, thus describing sets of related concrete types that are their descendants.

In [ ]:
abstract type Pointy{T} end
In [ ]:
Pointy{Int64} <: Pointy
Out[0]:
true
In [ ]:
Pointy{1} <: Pointy
Out[0]:
true
In [ ]:
Pointy{Float64} <: Pointy{Real}
Out[0]:
false

Types of motorcades

Cortices are an abstraction of function arguments without the function itself. The most important aspects of function arguments are their order and types. Therefore, a tuple type is similar to a parameterised immutable type, where each parameter is the type of a single field. For example, a tuple type of two elements is similar to the following immutable type:

In [ ]:
struct Tuple2{A,B}
    a::A
    b::B
end

However, there are three key differences:

  1. tuple types can have any number of parameters.
  2. Tuple types are covariant in their parameters: Tuple{Int} is a subtype of Tuple{Any}. Therefore, Tuple{Any} is considered an abstract type, and tuple types are concrete only if their parameters are concrete.
  3. Tuples do not have field names; fields are accessed only by index.

Tuple values are written in parentheses and commas. When constructing a tuple, the corresponding tuple type is generated on demand:

In [ ]:
typeof((1,"foo",2.5))
Tuple{Int64, String, Float64}
Out[0]:
Tuple{Int64, String, Float64}
In [ ]:
Tuple{Int,AbstractString} <: Tuple{Real,Any}
Out[0]:
true
In [ ]:
Tuple{Int,AbstractString} <: Tuple{Real,Real}
Out[0]:
false

Vararg tuple types

The last parameter of a tuple type can be a special value Vararg, denoting any number of finite elements:

In [ ]:
mytupletype = Tuple{AbstractString,Vararg{Int}}
Tuple{AbstractString, Vararg{Int64}}
Out[0]:
Tuple{AbstractString, Vararg{Int64}}
In [ ]:
isa(("1",1,2), mytupletype)
Out[0]:
true
In [ ]:
isa(("1",1,2,3.0), mytupletype)
Out[0]:
false

Named tuples

Named tuples are instances of type NamedTuple, which has two parameters: a character tuple specifying field names and a tuple type specifying field types. For convenience, NamedTuple types are rendered using the @NamedTuple macro.

In [ ]:
typeof((a=1,b="hello")) # prints in macro form
@NamedTuple{a::Int64, b::String}
Out[0]:
@NamedTuple{a::Int64, b::String}

Parametric primitive types

Primitive types can also be declared parametrically. For example, pointers are represented as primitive types, which will be declared in Julia as follows:

In [ ]:
# 64-bit system:
primitive type P{T} 64 end
In [ ]:
P{Int64} <: P
Out[0]:
true
In [ ]:
P{Float64} <: P
Out[0]:
true

Conclusion

This demonstration shows the basic data types and how they can be declared and used. More information can be found in the articles about integers and floating point numbers, and in the article about data types, which is the basis for this demonstration.