Welcome to the third part in the article series – ‘Getting started with Julia language’. Julia language is growing in popularity among data scientists, many surveys has listed it as one in top 5 programming languages for data scientists. Mostly due to the factors such as the faster execution time and speedier development process facilitated by the language. In this article, we’ll discuss variables and types in Julia language. In case you missed it, we’ve already covered how to set up Julia and Julia REPL and packages.
Like in every other programming language, variables are locations in the computer memory named by the user to hold some data related to the program.
In Julia, variable names are case-sensitive and a wide range of unicode characters (of UTF-8 encoding) are also supported. Variables can be assigned/created by using the conventional assignment operator “=”.
test_string = "Julia variable" pi_val = 3.14 count = 35 ❄ = -12
In the above examples, 3 variables namely “test_string”, “pi_val” and “count” are created/assigned with some values(data types).
Value in one variable can be assigned to another by simply following the n be equal to m convention
m = 10 n = m # value of m will be copied to n
Though there aren’t many restrictions in naming variables, some styling conventions are recommended.
A type constrains the values that an expression, such as a variable or a function, might take.
Types can be broadly classified into 2:
Julia’s type system is primarily dynamic and is augmented by the ability to specify types where needed, meaning that there is no need to tell Julia what type a particular value is unless you want to.
Based on Julia’s type hierarchy, types can be abstract or concrete.
Concrete Types can be again divided into two, primitive and composite.
Primitive Types – Primitive Types in Julia are the concrete types whose value is in the form of bits. Some examples of primitive types are: Int8 , Int16 , Int64 , Float16 , Float32 , String , etc…
Unlike most languages, with Julia you can declare your own primitive types.
Standard primitive types are defined as:
primitive type Float64 <: AbstractFloat 64 end primitive type Bool <: Integer 8 end primitive type Char <: AbstractChar 32 end primitive type Int64 <: Signed 64 end
Similarly we can define our own primitives with the following syntax:
primitive type Byte 8 end # Define the primitive Byte Byte(val::UInt8) = reinterpret(Byte, val) b = Byte(0x02) # Assign a byte value to b
Here, reinterpret is used to assign an unsigned integer with 8 bits (UInt8) into the Byte.
Note: You may be wondering what :: operator is for, don’t worry it will be explained in the follow-up tutorial
Composite Types – 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.
struct Foo Field1::Type Field2::Type end Example: # Define the struct struct Greet x y::String end # Creating object with constructor greet1 = Greet("Hello", "World") The values of a composite object can be accessed using the dot notation, greet1.x Output -> "Hello"
One interesting thing about composite types is that they are immutable in nature, i.e, we cannot change the value after instantiating the object. Fortunately, Julia also provides mutable types that have a similar syntax like the immutable composite types, the only difference is the usage of the mutable keyword in front of struct .
mutable struct Foo Field1::Type Field2::Type end Example: # Define the mutable struct mutable struct Num x::Float64 y::Float64 end # Creating object with constructor greet1 = Num(1, 5.3) # Update a value greet1.x = 4.5
You can change the values after instantiation. However, these values must comply with the type’s definition in that they must be convertible to the specified type (in our case Float64). For example, an Int64 input would be acceptable because you can easily convert an Int64 to a Float64. On the other hand, a string wouldn’t work because you can’t convert it to Float64 .
The :: operator has different functionalities in different contexts, let’s see what they are:
Declaring a (sub)type
In the context of a statement, such as a function, :: can be used to restrict the type of a variable.
# Declare the function function restrict_this_integer() x::Int8 = 32 x end # Call the function p = restrict_this_integer() typeof(p) # Output will be Int8
Note: Don’t worry about the function definition, it will be discussed in a follow up tutorial.
Asserting a type
In every other context, :: means ‘I assert this value is of this particular type’. This is a great way to check a value for both abstract and concrete type.
integer_value = 10 # Assert integer_value if of type Int64 integer_value::Int64 # Assert integer_value if of type Char integer_value::Char
The assertion for Int64 will return the value stored in “integer_value” and the assertion for Char will result in a TypeError — ERROR: TypeError: in typeassert, expected Char, got Int64 .
While we haven’t really discussed function inputs, you should be familiar with the general idea of a function — mapping a couple of arguments using some expression to a return value. Julia can ensure that a function only accepts values that we want.
Let’s define a function which takes 2 integers as input and provides their sum as output.
function add(x::Int64, y::Int64) x + y end # Call the function on 2 integers result = add(16, 14) Outputs -> 30 # Call the function on 2 floats result = add(2.0, 3.5) Outputs -> ERROR: MethodError: no method matching add(::Float64, ::Float64)
As you can see from the above examples, any types other than the specified one ( Int64 ) results in an error.