Turing Classes Part I

From Compsci.ca Wiki

Revision as of 00:14, 13 October 2008 by Wtd (Talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

At the end of this tutorial, you will find an attached .zip file. This file contains pretty much all the code we are using in this tutorial, as well as the image for the cover of our book. It also contains an example Button and Textfield class.

Contents

Requirements

There are many things you'll need to know to make it through this tutorial (the three parts of it). Of particular importance, you will need to know about Modules; a knowledge of them will make this tutorial seem very basic and straightforward. A knowledge of units is needed for Part III.

An Introduction

Have you ever heard the term, "Object Oriented Programming (OOP)", before? Perhaps from OOT: Object Oriented Turing. Object Oriented Programming means that one of the essential aspects of programming in an object oriented language is... objects! You will see objects everywhere. In a true object oriented (OO) language, such as Ruby, everything is an object; even fixed numbers, such as 42, are objects. But what is an object?

What are Objects?

In our daily lives, an object is something tangible, such as a book. Objects have various properties, such as a title, author, cover, pages, writing, information, page numbers, etc. We interact with objects. In our book example, we interact with the book by reading it. So, now we ask the question, "What are objects in programming?" In programming, objects can be defined as that which contain data and methods with which to interact with the outside world. If we were to make a book object, we would need a variable to store the title, the author, the text, and the cover image, and we would need a procedure to output the text, page by page.

"An object is a combination of data, and operations on that data. By "encapsulating" data within an object, and hiding it from the outside world, we can control the ways in which that data can be changed. Instead of the direct ability to manipulate that data, we provide only certain abilities, via methods."
wtd, in An Introduction to Java

Let's restate the above quotation because it is important. An object contains data and operations on that data. In other words, it contains variables and procedures/functions that work with those variables. Generally, the outside world sees the object and some of its procedures/functions, while other procedures/functions remain hidden, along with the variables. To understand why, let's examine a real-life object: the book. We interact with the book by reading it, but we cannot change what is written in the book. If we had a book object in Turing (this is actually more of an e-book), it would have a procedure to draw the cover, then output the text to the screen. We can then read the text off the screen, much like we read a real book. But once again we cannot change the text. The text of the e-book object is hidden within--it is "encapsulated". However, change is good, and objects generally offer some way to modify their data. We do that by calling certain special procedures/functions that are public to the outside world. Say we want to translate our book to French. The text of the book is hidden to us, but perhaps the object contains a public procedure that is viewable to the outside world, one that will translate the text of the book to French. By calling that procedure, we are translating the book into french by modifying the text of the object, the text that is hidden. We do not look at the text variable and manually translate its value to be in French. Rather, we call a procedure that carefully and acceptably translates the text into French.

What are Classes?

A class is a template for an object. It defines how we create objects. A class contains the template for creating a certain type of object. Let's look at a book class:

class Book
    export initialize, draw_cover

    var author : string
    var title : string
    % If this were a real book, we would use a file to store the text,
    % since Turing's strings can only hold 255 characters.
    var text : string
    var cover_image : int

    % All our books are going to use the same font set,
    % so we make them constant.
    const title_font := Font.New ("Impact:18")
    const title_colour := grey
    const author_font := Font.New ("Tahoma:14")
    const author_colour := green
    const text_font := Font.New ("Times New Roman:11")
    const text_colour := black

    proc initialize (author_, title_, text_ : string, cover_image_ : int)
        author := author_
        title := title_
        text := text_
        cover_image := cover_image_
    end initialize

    proc draw_cover (x, y : int)
        Pic.Draw (cover_image, x, y, picCopy)
        Font.Draw (title,
            round (x + Pic.Width (cover_image) / 2 - Font.Width (title, title_font) / 2),
            round (y + Pic.Height (cover_image) / 1.5), title_font, title_colour)
        Font.Draw ("By: " + author,
            round (x + Pic.Width (cover_image) / 2 - Font.Width (author, author_font) / 2),
            round (y + Pic.Height (cover_image) / 3), author_font, author_colour) 
    end draw_cover

end Book

Examine the initialize procedure. See how I put an underscore (_) after each parameter? That is entirely not necessary: your parameters could be (param1, param2, param3, param4), and it would still work fine. I added the underscore because I wanted to use the same variable names, author, title, text, and cover_image.

Don't worry too much about the draw_cover procedure. The big Font.Draw calls are mostly just centering the text. The important thing to see here is that this class is a template to create many different books. We've examined what a book is, and concluded that all books have an author, a title, text, and a cover_image, and that these things can vary. We decided that all our books (we are a publishing company) will have the same font and colour scheme, so we made that data constant, immutable. We made a procedure to draw the cover of the book, and we made an initialize procedure, to set all the variable data.

Note that this class is far from complete. We should also add procedures to draw the text, to handle the spine of the book, the cover of the book, page numbers, table of contents, etc.

Notice the export line, near the top. Let's look closer at what it does.

Scope--Export

We export our two procedures, making them callable by our soon-to-be book objects. A nicer way to look at this (and how most other languages look at it) is that initialize and draw_cover are public methods. Anything that is not exported is invisible to the outside world. We can also export variables. We will come back to this when we learn how to call methods on objects.

Scope--Import

We can import things into our classes. By default, things outside the class are not accessible within the class. For example, the following creates a warning:

var outer_variable := "I'm on the edge"

class TestClass
    var inner_variable := outer_variable
end TestClass

To avoid the warning, we need to import outer_variable.

var outer_variable := "I'm on the edge"

class TestClass
    import outer_variable
    var inner_variable := outer_variable
end TestClass

This creates no warning. You may need to import specific modules, such as the Input module, if you wish to use Input.KeyDown within your class.

class TestClass
    %import Input
    var keys : array char of boolean
    Input.KeyDown (keys)
end TestClass

This creates a warning, but if you uncomment the "import Input", the warning is avoided.

Together, you and I, we wrote a book, and we want to use our Book class to create our book object.

How Do We Create Objects?

To create an object in Turing, we have to use pointers. We have a tutorial on pointers, but it is rather complicated, and much of it is not all that necessary for our discussion of classes. To that end, I will briefly describe how we use pointers here.

There are two steps to creating an object. First, we must create a pointer that points to the class. The pointer variable is essentially how we access our book object.

var jack_and_jill : pointer to Book

Next, we need to create a new instance of the Book class.

new Book, jack_and_jill

We use new to create a new instance of the Book class, linked to our jack_and_jill pointer. You might be thinking that this seems repetitive: why have jack_and_jill point to Book, and then create a new instance of Book linked to jack_and_jill? Can't we just do one or the other, and Turing figure it out automatically? The answer is "No", for reasons that will become apparent in Part III of this tutorial.

Next, we need to learn how to call our procedures for our jack_and_jill object.

How Do We Call Procedures/Functions?

There are two ways to call methods (procedures/functions) from an object. For our current purposes, the two approaches are identical in every way except for their appearance: one is shorthand for the other. First, let's look at the original one:

ClassName (pointer_name).method_name (arguments)

Note that the use of class/method/variable naming is my own choice, and you're free to choose your own.

In the context of our Book class, it could look like this:

Book (jack_and_jill).initialize ("Cervantes et al.", "Jack and Jill", "Jack and Jill went up a hill to fetch a pail of water.", Pic.FileNew ("Jack and Jill.jpg"))

Notice a neat thing I've done here. I've passed a function, Pic.FileNew as a parameter to the initialize procedure. Pretty cool, and yet it makes perfect sense. Pic.FileNew is a function that returns an integer, and that's exactly what the cover_image_ parameter is asking for.

Now, let's see what the shorthand approach looks like.

pointer_name -> method_name (arguments)
jack_and_jill -> initialize ("Cervantes et al.", "Jack and Jill", "Jack and Jill went up a hill to fetch a pail of water.", Pic.FileNew ("Jack and Jill.jpg"))

It seems that the shorthand approach is nicer, but I urge you to avoid it because it makes an assumption about the class that the method belongs to. In Part III, when we start building a network of classes, we will need to specify which class we are calling our method from, and that specific class can be different than the class Turing automatically assumes we are calling from. In my example classes at the end of this tutorial, sadly, I did not follow my own advice here, for that was before I learned about Turing's inheritance, which is the topic for Part III.

Here is our complete code, thus far:

class Book
    export initialize, draw_cover

    var author : string
    var title : string
    % If this were a real book, we would use a file to store the text,
    % since Turing's strings can only hold 255 characters.
    var text : string
    var cover_image : int

    % All our books are going to use the same font set,
    % so we make them constant.
    const title_font := Font.New ("Impact:18")
    const title_colour := grey
    const author_font := Font.New ("Tahoma:14")
    const author_colour := green
    const text_font := Font.New ("Times New Roman:11")
    const text_colour := black

    proc initialize (author_, title_, text_ : string, cover_image_ : int)
        author := author_
        title := title_
        text := text_
        cover_image := cover_image_
    end initialize

    proc draw_cover (x, y : int)
        Pic.Draw (cover_image, x, y, picCopy)
        Font.Draw (title,
            round (x + Pic.Width (cover_image) / 2 - Font.Width (title, title_font) / 2),
            round (y + Pic.Height (cover_image) / 1.5), title_font, title_colour)
        Font.Draw ("By: " + author,
            round (x + Pic.Width (cover_image) / 2 - Font.Width (author, author_font) / 2),
            round (y + Pic.Height (cover_image) / 3), author_font, author_colour)
    end draw_cover

end Book

var jack_and_jill : pointer to Book
new Book, jack_and_jill

Book (jack_and_jill).initialize ("Cervantes et al.", "Jack and Jill", "Jack and Jill went up a hill to fetch a pail of water.", Pic.FileNew ("Jack and Jill.jpg"))
Book (jack_and_jill).draw_cover (50, 50)

You can find the image I've used as the cover_image as an attachment at the end of this tutorial.

Now let's take a closer look at how this is all working.

A Closer Examination

When we create the new instance of the class linked to our pointer (by using new), the object is initialized. All the code excluding our methods is executed. In the case of our Book class, the code that makes our variables and constants is executed. This makes sense, but let's take a closer look.

The code that creates the variables and constants and defines our methods is written in our class, but the variables, constants, and methods (henceforth known as data) belong to the object, jack_and_jill. What would happen if the data belonged to the class? We only have one class, so we would only have one set of data. We can write many books though, and use the same template (class) for our other books. Each book needs its own title, its own author, etc. Thus, the data must belong to the object, and there must be a set of data for each object we create.

Returning to Export

Earlier I mentioned we could export variables as well as procedures/functions. Let's examine that now.

class TestClass
    export test_var
    
    var test_var := 5
end TestClass

var p : pointer to TestClass
new TestClass, p

put TestClass (p).test_var

This outputs 5, the value of test_var. We can also change the value of our variables:

class TestClass
    export var test_var
    
    var test_var := 5
end TestClass

var p : pointer to TestClass
new TestClass, p

put TestClass (p).test_var
TestClass (p).test_var := 42
put TestClass (p).test_var

However, it is important to note that giving full access to the data of our object is generally frowned upon. As discussed earlier, objects allow the outside world to manipulate their data by calling certain methods. To that end, the following class essentially gives the outside world full access to its data (read & write access), but in a more conventional style.

class TestClass
    export set_test_var_to, value_of_test_var
    
    var test_var := 5
    
    proc set_test_var_to (value : int)
        test_var := value
    end set_test_var_to
    
    fcn value_of_test_var : int
        result test_var
    end value_of_test_var
    
end TestClass

var p : pointer to TestClass
new TestClass, p

put TestClass (p).value_of_test_var
TestClass (p).set_test_var_to (42)
put TestClass (p).value_of_test_var

Q&A

At this point, I feel the most practical example of classes in action is seen in GUI (Graphical User Interface). If you look at the code for Turing's GUI, you will see a network of classes all over the place. For now, we only need look at one class. The simplest and most useful one is the Button class, and you can make it yourself.

Question 1

Make a Button class, similar to the buttons Turing's GUI uses.

Answer 1

I've attached my Button class (see the end of this tutorial for the attachment), which I made about a year ago. It could use some attention, if anyone is willing to clean it up (or possibly rebuild it, showing how to build a Button class in stages).

Question 2

Make a TextField class, similar to the address bar of an internet browser, where you enter the URL.

Answer 2

My TextField class (and examples) can be found here, in my Database with Custom GUI thread.

Note that, in my Button and TextField code, you will find me declaring pointers to classes like this:

var pointer_name : ^ClassName

This is merely shorthand to

var pointer_name : pointer to ClassName

It is identical in every way, except appearance.

Conclusion & Further Studies

This tutorial is in three parts. This tutorial has covered the basics of classes. Know that to really get a good understanding of classes, you will need to write some of your own classes and put them to use. I've suggested two possible classes to make, though the options are many. When you are ready, read Part II of this tutorial, which deals with object interaction.

Personal tools