Types in Julia are basically the data elements of various different sizes and functionality. Every value(not variable) in a program is bound to be of some type. Variables are simply names bound to some values. Defining a value’s type is done either by the programmer or by the compiler itself. It is always considered feasible to define the type of the value before executing the source code in order to achieve fast execution and making your code less prone to errors.
Example: Consider the following three values:
10 -> Integer, 10.5 -> Float, 10/3 -> Rational
Each value here is of a different type. The integer value is different from a float value and from the rational value and similarly, the other two values are different from each other. Now, if we have to add these three values or perform some other operation on these values then it will be a difficulty because the values are not of the same type. In order to add these values, we need to convert the other two values to be of the same type as the third one.
Type System is basically divided into two categories, based on the time of its definition. These are:
- Static Type System
- Dynamic Type System
Static Type System: In a static type system, every executable program expression is defined with a type prior to its execution. This is done at the programmer’s end and is feasible for faster execution speed of the code.
Dynamic Type System: In a dynamic type system, the compiler has to decide the type of the values at the time of code compilation. This will result in the code to take some extra time for execution.
Julia uses Dynamic Typed System to write variable values but it also accepts statically typed definitions to provide advantage of indicating certain values are of specific types. By default, Julia assigns the untyped values with any type required. Thus the programmer can write any Julia function without ever defining the types explicitly. But, providing explicit type to the value results in better code readability and also it makes it easier to find errors in the code. Hence, it is not necessary to use the Type system in Julia but using it will make the code clearer, simpler, faster and robust.
Declaration of Types
Types in Julia are declared with the help of the :: operator. This operator is read as ‘is an instance of’ when appended to a value or an expression. It can be used anywhere in the program to show that the value on the left-hand side is an instance of the type defined on the right side of the :: operator.
Types are declared to make a proof of the assertion to confirm that the program is working fine and to provide the compiler some extra information about the type of the expressions and values which will result in faster and more efficient code.
Now, you might think that what if the programmer is unaware of the type and assigns a wrong type to the value or expression. This is handled by an exception in Julia. If the value on the left side is not of the type that is declared on the right side, an exception is generated and the code execution will stop there. Because the compiler expects the computed value on the left side to be of the type specified on the right side. The exception raised will state the expected type from the left side(as defined) and what is actually being produced.
In the above snippet, it can be seen that when the expression on the left side is defined with a Float type, then it generates an Error on execution because the computed value on the left was expected to be of Float64 type as declared but it is of the type Int64. Later when the Type system was changed to Int, it computed the value on the left side and produced the output.
Using Type definition with Assignment operator: When used with the assignment operator, the :: operator if used to make the variable on the left-hand side a typed value, then it will restrict that variable to always hold the value of the same type, just like the statically typed variables in C language. Every value assigned to the variable, if not of the same type will be converted to the same type with the use of the
Note: Declaration of Typed variable is limited to local variables only. For now, Julia doesn’t allows to Type global variables.
Declaration of Type for functions is also allowed in Julia. This will result in the function to always convert and return the computed value in the form of the specified Type.
In the above code, the function should return the Integer value but due to type declaration of the function, the output of the computed value is converted into Float64(desired type).
Concrete Types and Abstract Types
Julia allows every object to have a type, but it is not certain to have instances of all the types. The types that can have instances are referred to be as concrete types. Concrete types are not allowed to have any subtypes. Subtypes are the sub-entities of a Type. If a type can be further divided into different types, then those parts are called subtypes. For ex- Strings, Rational, bool, etc.
Abstract types are those whose subtypes exist. For ex- Any, Number, etc. Though we can’t create objects of the Abstract types but with the help of Abstract types, a programmer can write codes that can work with any of the subtypes of the specified type.
The above code can accept a number of any subtype, i.e. it will accept Float64, Int64, etc. and will return the output in the same form. Because here, x is typed with ::Number, so x must be subtype of Number. It will generate an error if the value of x is something other than a subtype of Number like String, array, etc.
Subtypes of a type can be written with the use of <: symbol. This symbol means that the left side
is a subtype of the right side. It can also be used in an expression to test if the left side operand is a subtype of the right operand. It will return true for the same and false if it is not a subtype of the specified type.
Abstract types can be used to provide default implementations to concrete types. If we take an example in which the default implementation is of Any type. Then the compiler will assign the respective subtypes to the values as per the need of the computation.
On execution of the above code, the compiler will auto-assign the type of the arguments passed as the type of the values in the function.
Concrete types can be further divided into two types:
- Primitive Types
- Composite Types
Primitive Types in Julia are the concrete types whose value is in the form of bits. Integers and Float values are an example of Primitive types. Julia has a predefined set of standard primitive types but it also allows to create your own types.
Syntax for declaring a primitive type:
end primitive type <: end
Examples for Primitive Types are Int8, Int16, Int64, Float16, Float32, String, etc.
Abstract types can not be used to store values in the memory, the compiler will automatically assign a primitive type to the value. If we check for the size of the Abstract type Integer, then it will raise an error because Integer doesn’t have a fixed size, but we can check for the size of its Primitive types i.e. Int8, Int16, Int64, etc.
Composite types in Julia are a collection of named fields which can be individually treated as single values of specific types. Composite types can be declared with the use of struct keyword.
Field_name1 Field_name2::Type Field_name3::Type end
Here, the fields which are not specified with any type are set with the default type Any. The object of this Composite type can be created with the use of constructors.
As we can see that in the above code, the composite type is created with the use of struct keyword and all the fields are individually of Primitive types. If we check the type of the object created with the use of the constructor, then it shows the struct that has been created.
Note: Composite types created with the use of struct keyword are immutable, which means they cannot be modified after their creation.
Mutable Composite Types
The composite types defined with the use of struct are of immutable types as explained above. But, Julia also allows creating Composite types of Mutable types, which means that the value of its field can be modified even after their creation. This is done by the use of keyword mutable struct. If a composite type is declared with mutable struct instead of struct, then the value of its instances can be modified anytime.
Field_name1 Field_name2::Type Field_name3::Type end
Values of a mutable struct can be modified just by passing the new value to the field.