Ruby Stripped, Part 1: Introduction to Classes
As the title says, this is an introduction to classes in Flowstone . It is not a reference, but will give you enough at hand to make use of classes and hopefully get you hooked enough to expand your experiences. For a reference, have a look at ruby-doc.org: Ruby 1.9.3 reference
What is a class?
A good question. I try to answer without much technical terms. A class is a structure that encloses variables focused on a specific task and functions that work with and tweak those variables. It is an object-oriented approach to programming. Instead of plain code all over the place, you structure it, so that everything belonging to an object is in one place. This makes code highly maintainable.
If you have an object called Array, you don’t just have a place in memory to store an index sorted list of variables. You also find all functionality dealing with arrays in this object. ‘Object’ as you might already have guessed is a synonym for ‘class’.
In Ruby, the variables belonging to a class are called ‘attributes’, and the functions are called ‘methods’. So, working with a class means defining a class, adding attributes to it and methods, ensuring the functionality of the class.
A class in Ruby
Let us have a look at the very least needed to create a valid class in Ruby:
The keyword ‘class’ starts the class block, followed by that class’ name. The keyword ‘end’ closes the class block. Note that the class name is written with a capital letter: “Myclass”. This is no coincidence. Ruby expects class names to start with a capital letter (just as it expects method names to start with a small letter).
We can now access the class by referencing an instance of it:
The info pane shows the result of the class method ‘class’. It tells which template was used to create this instance. ‘heureka’ is of type ‘Myclass’. Not too surprising, but good to know that Ruby understood what we wanted it to do.
Now that we have a class, we sure want to fill it with content. A class without can also have its purpose, but that’s too advanced for this introduction. With the class method ‘new’ we not only created an instance of our class. We also instructed Ruby to look inside the class for a method called ‘initialize’. If it exists it will be executed during creation. That helps us a lot setting up our attributes.
Before we can go on, I have to make sure, you are aware of the different types of variables in Ruby. Especially local and instance variables. In general, variables have a scope and they are only existing within their scope. A local variable is identified by just a plain name and its scope is where it was created, either an instance or a method. An instance variable is preceded by an @ and its scope is an instance including every method within that instance.
A class of its own
The caption might be misleading. In Ruby there is only one class of its own and that is the ‘object’ class. All other classes are children of that one base class. A class can build upon another class, but never inherit more than one class. We will come to that later. For now we will build our first funtional class. It shall represent a point. For a point we need two coordinates, and we want them to be floats so that we can use Flowstones grid units.
Note that x and y are instance variables. We need them to be so, because else we couldn’t reference them in other methods of the class, practically rendering them useless. But although we have the attributes, we can’t access them. That is because they are protected. Nobody but the class shall have access to them directly. To access them, we need to reference them indirectly. That’s what methods are for.
First functionality should be to set and get the coordinates. The getter function is a method that simply returns the value of the coords. The setter function is a method that changes the value of the coords. Since it is convenient to do this the same way as we apply values to a variable (e.g. a = 1.5), Ruby offers a special method definition. If we add a “=” directly after a method’s name it indicates that this method can be used just like a variable.
Click on the thumbs to see them at full size. The image to the far left shows you how getter and setter functions are normally defined. The names of the methods doesn’t need to fit the variables’ names. Instead of x and y you could also use bird and fish, or stallone and schwarzenegger, just anything that makes sense to either yourself or the people that will use your class later.
The next image shows a shortcut for the getter functions. Since getting and setting attributes of a class is such a common task, Ruby provides you with template methods. Those names start with “attr_”, which is an abbreviated “attribute”. For a getter function creation the method name is “attr_reader”. Now you need to provide parameters to that method . Those are given in the form of symbols. I won’t go into details about symbols, just imagine those as another way of writing variables. A symbol starts with a double colon (:) and any prefixes are omitted. So, an instance variable @x becomes as a symbol. If you want Ruby to create two getter functions for your instance variables @x and @y, the method would be called as “attr_reader :x, :y”. Accordingly, the setter function is called. It has the name “attr_writer”, so a call of “attr_writer :x, :y” creates the setter functionality for @x and @y. Last but not least you can also create a pair of getters and setters at once by calling the method “attr_accessor :x, :y”
If it is that easy to create those getter-setter-pairs, why isn’t that the default way of doing it? It simply is bad programming. At any time, a class should be the only one having direct access to its instance variables. If you use attr_writer or attr_accessor, you give the instance variables out of the hands of the class. From outside of the class those variables can now be set to anything, without the class getting notice. For example, our point class expects numbers for x and y, but with attr_writer or attr_accessor we can set x and y for example to strings, or even to other point class instances! Also, using “attr_”-methods bounds you to the variables’ names, which is not always wanted. Be careful and think about your class and its purposes. Only use any “attr_”-method, if you are 500% sure that it won’t lead to consequential errors.
Extending our class
Back to our class. It is essentially functional now. We can create an instance of it and set and get x and y. But sometimes we already know the values of x and y that we want to assign. Wouldn’t it be great to pass those values on instance creation? It saves us two additional lines of codes. And indeed that is possible. As you already saw, methods can be given parameters. And “initialize” is just the same type of method as any other. So we can define parameters to it.
On the left you see that we created to local variables as parameters for the method (remember the scope I talked about earlier). The instance variables will be set to whatever x and y are when the instance is created, in this case 2.0 and 4.0
But if we don’t know the values we can’t provide parameters on instance creation. Calling “Point.new” without parameters on the image to the left would raise an error. That’s why Ruby offers the initialization of parameters with default values. This is shown on the second image. If we now call “Point.new” without parameters, Ruby initializes them to 2.0 and 4.0, instead of raising an error.
Our point class is pretty functional right now. We can create an instance of it, even with passing parameters, we can change and look at the coordinates. Now imagine we have a code with two point instances and we want to compare them. Of course, we could write something like this:
if point1.x == point2.x and point1.y == point2.y #point1 matches point2 end
But isn’t it tedious to write something like that for each possible point that needs comparison? Imagine using hundreds of points!
Remember, Ruby is object oriented, so there are solutions to our problem. If we are only interested in comparing equality, then there already is a method for that, defined in the parent of all classes, ‘object’. We can type the following:
coords = Point.new coords2 = Point.new coords == coords2
But it will return ‘false’. And that is because equality currently is tested for object equality. So we are asking, if coords and coords2 are the same object. It obviously is not, since we created two different instances. What we need is a comparison based on values of the instances. And we can do so by defining our own equality comparison method, which will override the default one.
The name of the method is ‘==’ and it takes one argument, ‘other_point’. You can name it as you like, I decided to name it like this to make it obvious, what it references. The parentheses are important here to signalize which expression result we want to return. If you omit them, Ruby interprets the line as “First please return the result of self.x == other_point.x and after you returned that, please compare self.y and other_point.y”. That of course doesn’t make sense and so you would get a “void value expression”-error. So, don’t forget the parentheses! As soon as this method is implemented you get the opposite, the inequality comparison, for free. Just try it, and type ‘coords != coords2′.
Let us go one step further. A point could also be interpreted as a vector. As such it has a length and a direction (or angle). And by comparing vectors, we could not only test for equality, but also for <, >, <= and >=. I bet you would find it pretty cool if your point class would behave that way. I will not explain vectors, you can read about them on the internet, but I will use vector comparison to introduce you to another, more complex method, the so called spaceship (‘<=>’). That method has to return one of three values: -1, 0 or +1. If you think that the current class is less than the other class, you have to return -1, if you think it is greater you return +1, else 0, which means they are equal. Pretty much straight forward. The only thing different to our last attempt is that the spaceship is not defined in the parent, but in a module. Modules are collections of methods (and even classes) for generic use. They are only defined once and then are usable in each class that wants to use them. The keyword for Ruby to make use of a module within a class is “include”, followed by the name of the module.
def Myclass include Comparable end
Module names start with a capital letter, just like class names. And the module that keeps the definition of the spaceship is called ‘Comparable’. The following image shows how I implemented the spaceship in our point class, omitting the accessor definitions to save some space.
Now we can test any point with another point for everything needed: ==, !=, >=, <=, > and <.
And with that I will end this part. You should have a solid foundation now to create your own classes and make use of them. Of course there will be questions, and I will try to answer them, if possible.
Here is a schematic containing our final class ruby_classes_part1.fsm