Tutorial: Classes

How to create our own custom types.

Classes in Quorum

When programming a computer, it is often convenient to define custom structures that contain data and can run actions on that data. For example, while we hypothetically could create a computer program that represents a dog as a set of 1s and 0s, it might be mentally difficult for us to remember the code. Further, if our dog had certain operations we wanted it to do (e.g., walk, bark), organizing and controlling the structure might also be difficult. In Quorum, we use what are called classes to represent custom structures. Here is a first example:

class MyClass
     action Main
          output "Hello, World!"
     end
end

This class has several key components. First, the word class itself is telling Quorum that we want to create our own custom structure and that we want to give it a name. In this case, that name is MyClass. Second, notice that just like in the previous tutorial on actions, we need an action named Main, which starts our program. Third, we have an output message with words surrounded in double quotes, which tells Quorum to output information to the screen. Finally, while there is no way to know from the code itself, only one class is allowed per file.

Now, one obvious question that may arrise is this: if all this program does is outputs something to the screen, do we even need a class?

output "Hello, World!"

For a trivial program, the answer is no, not really, but Quorum is sneaky. When we do not specify a class, Quorum actually builds one for us anyway. So, in the above program, Quorum is making us a class behind the scenes. Basically, Quorum wraps the above program with a class, which it runs in a Main action by default, which the programmer does not need to specify. In such a case, the name Quorum chooses for the class is the file name, minus the extension. So, the above code in a file named MyClass.quorum would have the name MyClass. So why then bother? The reason is because classes allow us to, amongst other things, organize and structure our code for reuse. One way we do this is with member variables.

Member Variables

Just as actions can store local variables, so can classes. This concept is useful because it allows multiple actions to share a particular variable. Here is a first example where two actions share the variable named addToMe

class Main
   integer addToMe = 0
   action Main
       AddToTheVariable()
       output addToMe
   end
   action AddToTheVariable
       addToMe = addToMe + 1
   end
end

Notice that this class has two actions in it. The first, Main, starts our program and then calls the action AddToTheVariable, which jumps the program to that point. Next, the program then adds 1 to the variable addToMe, finishing the AddToTheVariable action and jumping back to Main. After that, it outputs the member variable and exits.

The reason this works is because the variable addToMe is in a different scope, effectively where in a computer's memory something lives, compared to a variable writen inside of an action. Essentially, both actions in the class can see all member variables and then use them. When one action modifies the member variable, all other actions are referencing the same place in memory, and as such, the change takes place everywhere.

As one final point on member variables, note that some programming languages give different names to the same or similar concept. Specifically, member variables can be called fields, instance variables, or other similar names. While there are differences between the languages, the concepts are similar.

Public and Private

Both member variables and actions can have a special tag added to them to provide them with an access property. Essentially, it is possible to prevent accidental access to certain actions or variables that live in a class by using the words public and private. Here is an example:

class Main
   public integer addToMe = 0
   action Main
       AddToTheVariable()
       output addToMe
   end
   private action AddToTheVariable
       addToMe = addToMe + 1
   end
end

Notice that we have now specified the integer addToMe as public. While we cannot run an example in the online version of Quorum, as it currently only supports executing one class, this keyword allows other classes to access that variable, whereas private only allows the current class to provide access. Second, public and private have the opposite meaning, allowing (public) or preventing (private) access. So by making an action private, other classes cannot access it. By default, member variables are private and actions are public. The words public and private can only be used by member variables and actions, but not local variables inside of an action.

Instantiation

Classes are a useful concept from the perspective of being able to reuse functionality that others have written. By themselves, classes are like a plan for a house. A plan for a house shows how to build it, but is not a house itself. As an analogy, the same is true for classes.

Let's take a tangible example of functionality that we may not know how to write ourselves, a Random Number Generator. Now, generating numbers that appear to be random is a difficult problem, for many reasons. Scientists need random number generators for different purposes. For example, in cryptography, code solving, we need numbers to be generated that are extremely hard to predict. On the other hand, in video games and many other applications, we might prioritize generating random looking numbers quickly over other properties. In Quorum, we use a standard algorithm in our Random number generator called the Mersenne Twister, which is acceptable for many, but not all applications and is commonly used in programming. We can read more about that algorithm, and its uses, here:

The Wikipedia page for Mersenne Twister

Implementing a Mersenne Twister on our own can be tricky to get right, but since a class is built into Quorum already, we do not have to. Instead of learning and testing our own, we can use the algorithm built into Quorum. Here is an example of how:

use Libraries.Compute.Random
Random generator
integer value = generator:RandomIntegerBetween(0, 10)
output value

This example instantiates a class named Random. When this happens, a variable is created in memory, named generator, and we can then use it. Behind the scenes, when we call RandomIntegerBetween(0,10), while we as programmers only know the algorithm is computing a random number between 0 and 10, Quorum is running a Mersenne Twister to do that job. In other words, writing and instantiating classes has potentially saved us time and effort. If someone writes code to help us be more productive (e.g., games libraries, physics libraries, statistics libraries) we can use that work and be more productive ourselves.

Accessing Member Variables with 'me'

As one final point, there is a special keyword that allows us to distinguish between member and local variables with the same name, me. Suppose we had a class with a member and local variable named value, like so:

class Main
     integer value = 0
     action Main
          integer value = 5
          output value
     end
end

We might assume that this code outputs 0, because we are outputting value. However, because value is used locally, it overrides (or masks) the member variable and outputs 5. We can get around this by using the me keyword. Thus, the following code will output 0 because we are explicitly requesting the member, not the local, variable:

class Main
     integer value = 0
     action Main
          integer value = 5
          output me:value
     end
end

Next Tutorial

In the next tutorial, we will discuss Coding Guidelines, which describes some basic conventions that we can follow while coding..