Scopes, Environments, and Objects Top Encapsulation, Inheritance and PolymorphismObjects Contents

Objects

In the Scam world, an object is simply a collection of related variables. You've already been exposed to objects, although you may not have realized it. When you created a variable, you modified the environment, which is an object. When you defined a function, you created an object. To view an object, we use the predefined function pp.7 Evaluating the code:

    (define (square x)
        (* x x)
        )
    
    (ppTable square)

yields something similar to the following output:

    <object 10435>
             __label  : closure
           __context  : <object 10381>
                name  : square
          parameters  : (x)
                code  : (begin (* x x))

We see that the square function is made up of five fields.8 These fields are: __label, __context, name, parameters, and code.

Usually, an object lets you look at its individual components. For example:

    (println "square's formal parameters are: " (get 'parameters square))

yields:

    square's formal parameters are: (x)

We use the function get to extract the fields of an object. The first argument to get is an expression that should resolve to a symbol to be gotten, while the second is the object holding the field.

Creating objects

It is easy to create your own objects. First you must make a constructor. A constructor is just a function that returns the predefined variable this. Suppose you want a constructor to create an object with fields name and age. Here is one possibility:

    (define (person)
        (define name)
        (define age)
        this
        )

We can create an object simply by calling the constructor:

    (define p (person))

The variable p now points to a person object and we can use p and the set operator to set the fields of the person object:

    (set 'name "Boris" p)
    (set 'age 33 p)
    (inspect (get 'name p))

The set function takes three arguments. The first should evaluate to the field name, the second to the new field value, and the third to the object to update. Evaluating this code yields the following output:

    (get 'name  p) is Boris

Scam allows an alternative form for getting values from an object. This form uses function call syntax. For example, the following two expressions are equivalent:

    (get 'name p)
    (p 'name)

The advantage of the latter form is that if the retrieved field is itself an object, you can chain field names together. For example if n points to a linked list:

    (define (node value next) this)
    (define n (node 'a (node 'b (node 'c nil))))

you can get the value of the third node in the list with either expression:

    (get 'value (get 'next (get 'next n)))
    (n 'next 'next 'value)

To set the value of a field using a similar syntax, one calls the set* function. Here, both calls set the value of the third node in the list to 5:

    (set 'value 5 (get 'next (get 'next n)))
    (set* n 'next 'next 'value 5)

Note that for set, the object is the last argument while for set*, the object is the first.

Initializing fields

It is often convenient to give initial values to the fields of an object. Here is another version of person that allows us to do just that when we create the object:

    (define (person name age) this)
        
    (define p (person "Boris" 33))
        
    (inspect (p 'name))

The output is the same:

    (p 'name) is Boris

In general, if a field is to be initialized when the object is constructed, make that field a formal parameter. If not, make the field a locally declared variable.

Adding Methods

Objects can have methods as well.9 Here's a version of the person constructor that has a birthday method.

    (define (person name age)
        (define (birthday)
            (println "Happy Birthday, " name "!")
            (++ age)
            )
        this
        )
        
    (define p (person "Boris" 33))
    ((p 'birthday))
    (inspect (p 'age))

The output of this code is:

    Happy Birthday, Boris!
    (p 'age) is 34

In summary, one turns a function into a constructor by returning this from a function. The local variables, including formal parameters, become the fields of the function while any local functions serve as methods.

The variable this is not this

It is tempting to think that the predefined variable this is equivalent to this in Java. In Scam, this always refers to the current scope/environment, while in Java, this refers to the current object. The difference is noticable in object methods. For example, consider the following constructor:

    (define (f x)
        (define (g y)
            this
            )
        this
        )

If we create an f object:

    (define a (f 0))

and then save the return value of the g method:

    (define b ((f 'g) 1))

then a and b are not the same objects; b would be a `sub-object', as it were, of a. In fact:

    (eq? a b)

returns false, while:

    (eq? a (b '__context))

evaluates to true. To have the g method return a pointer to the object created by f, we would have g return its context:

    (define (f x)
        (define (g y)
            __context
            )
        this
        )

Objects and Types

If you were to ask an object, "What are you?", most would respond, "I am an environment!". The type function is used to ask such questions:

    (define p (person "betty" 19))
    (inspect (type p))

yields:

    (type p) is environment

This is because the predefined variable this always points to the current environment and when we return this from a function, we are returning an environment. Because environments are objects and vice versa, making objects in Scam is quite easy.

While the type function is often useful, we sometimes would like to know what kind of specific object an object is. For example, we might like to know whether or not p is a person object. That is to say, was p created by the person function/constructor?. One way to do this is to ask the constructor of the object if it is the person function. Luckily, all objects carry along a pointer to the function that constructed them:

    (define p (person "veronica" 20))
    (inspect (p '__constructor 'name))

yields:

    (p '__constructor 'name) is person

So, to ask if p is a person, we would use the following expression:

    (if (and (eq? (type p) 'environment)
             (eq? (p '__constructor 'name) 'person)) ...

Since this construct is rather wordy, there is a simple function, named is?, that you can use instead:

    (if (is? p 'person) ...

The is function works for non-objects too. All of the following expressions are true:

    (is? 3 'INTEGER)
    (is? 3.4 'REAL)
    (is? "hello" 'STRING)
    (is? 'blue 'SYMBOL)
    (is? (list 1 2 3) 'CONS)
    (is? (array "a" "b" "c") 'ARRAY)
    (is? (person 'veronica 20) 'object)
    (is? (person 'veronica 20) 'environment)
    (is? (person 'veronica 20) 'person)

Other objects in Scam

While environments constitute the bulk of objects in Scam, two other object types are built into Scam. They are closures (seen the the first section) and error objects. The main library adds in a third object type known as a thunk.

An error object is generated when an exception is caught. The fields of an error object are code, value, and trace. A thunk is an expression and an environment bundled together. Thunks are used to delay evaluation of an expression for a later time. The fields of a thunk are code and __context. You can learn more about error objects and thunks in subsequent chapters.

Fun with objects

Because of the flexibility of Scam, one can add Java-like display behavior to objects. In Java, if an object has a method named toString, then if one attempts to print the object, the toString method is called to generate the print value of the object.

Here is an example:

    (define (person name age)
        (define (birthday)
            (println "Happy Birthday, " name "!")
            (++ age)
            )
        (define (toString)
            (string+ name "(age " age ")")
            )
        this
        )

    (define p (person "boris" 33))

Given the above definition of person, printing the value of p:

    (println p)

yields:

    <object 23452>

Uh oh. The toString method wasn't called! This is because the current version of println, defined in main.lib, does not understand the toString method. No problem here, we'll just reassign display, since println calls print and print calls display to do the actual printing:

    (define (display  item)
        (if (and (object? item) (local? 'toString item))
            (__display ((item 'toString)))
            (__display item)
            )
        )

The main library binds the original version of display to the symbol __display for safe-keeping.

Note that this new version of display is only found in the local environment, so the current versions of println and print, defined in an outer scope, cannot see the new version of display in the current scope. To do so would be a scope violation. We solve this problem by cloning the two printing functions. The process of cloning produces new closures with the local environment as the definition environment. In all other respects, the cloned function is identical. Thus, when the new print calls display, the new, local version will be found.

    (include "reflection.lib")

    (define print (clone print))
    (define println (clone println))

    (println p)

The reflection library must be included to access the clone function.

Now, printing the object p yields:

    boris (age 33)

lusth@cs.ua.edu


Scopes, Environments, and Objects Top Encapsulation, Inheritance and PolymorphismObjects Contents