rebol document

Chapter 10 - Objects

REBOL/Core Users Guide
Main Table of Contents
Send Us Feedback

Contents:

1. Overview
2. Making Objects
3. Cloning Objects
4. Accessing Objects
5. Object Functions
6. Prototype Objects
7. Referring to Self
8. Encapsulation
9. Reflective Properties

1. Overview

Objects group values into a common context. An object can include scalar values, series, functions, and other objects. Objects are useful in dealing with complex structures as they allow related data and code to be encapsulated and passed as a single value to functions.

2. Making Objects

New objects are created with the make function. The make function requires two arguments and returns a new object. The format of the make function is:

new-object: make parent-object new-values

The first argument, parent-object , is the parent object from which the new object is made. If no parent object is available, as when defining an initial object, use the object! data type, as shown below:

new-object: make object! new-values

The second argument, new-values , is a block that defines additional variables and initial values for the new object. Each variable that is defined within the block is an instance variable of the object. For example, if the block contained two variable definitions, then they would be variables of the object:

example: make object! [
    var1: 10
    var2: 20
]

The example object has two variables that hold two integers.

The block is evaluated, so it can include any type of expression to compute the values of the variables:

example: make object! [
    var1: 10
    var2: var1 + 10
    var3: now/time
]

Once an object has been made, it can serve as a prototype for creating new objects:

example2: make example []

The above example makes a second instance of the example object. The new object is a clone of the first object. New values for the second object are set in the block:

example2: make example [
    var1: 30
    var2: var1 + 10
]

In the example above, the example2 object has different values than the original example object for two of its variables.

The example2 object can also extend the object definition by adding new variables to it:

example2: make example [
    var4: now/date
    var5: "example"
]

The result is an object that has five variables: Three that came from the original object, example , and two new ones.

The process of extending the definition of an object can be repeated any number of times.

You can also create an object that contains variables that are initialized to some common value. This can be done using a cascaded set of word definitions:

example3: make object! [
    var1: var2: var3: var4: none
]

In the example above, the four variables are set to none within the new object.

To summarize, the process of creating an object involves these steps:

  • Use make to create a new object based on a parent object or the object! data type.
  • Add any new variables that are defined in the block to the new object.
  • Evaluate the block, which causes the variables defined in the block to be set to the values in the new object.
  • The new object is returned as a result.

3. Cloning Objects

When you use a parent object to make a new object, the parent object is cloned rather than inherited. This means that if the parent object is modified, it has no effect on the child object.

As an example, the following code creates a bank account object, whose variables are blank:

bank-account: make object! [
    first-name:
    last-name:
    account:
    balance: none
]

To use the new object, values can be provided to create an account for a customer:

luke: make bank-account [
    first-name: "Luke"
    last-name: "Lakeswimmer"
    account: 89431
    balance: $1204.52
]

Since new accounts are made on a regular basis, it helps to use a function and some global variables to create them:

last-account: 89431
bank-bonus: $10.00

make-account: func [
    "Returns a new account object"
    f-name [string!] "First name"
    l-name [string!] "Last name"
    start-balance [money!] "Starting balance"
][
    last-account: last-account + 1
    make bank-account [
        first-name: f-name
        last-name: l-name
        account: last-account
        balance: start-balance + bank-bonus
    ]
]

Now a new account object for Fred would only require:

fred: make-account "Fred" "Smith" $500.00

4. Accessing Objects

Variables within objects are accessed with paths. The path consists of the object name followed by the name of the variable. For example, the following code accesses the variables in the example object:

example/var1

example/var2

Here are examples using the bank-account object:

print luke/last-name
Lakeswimmer
print fred/balance
$510.00

Using a path, the variables of an object can also be modified:

fred/balance: $1000.00
print fred/balance
$1000.00

You can use the in function to access object variables by fetching their words from within their object context:

print in fred 'balance
balance

The balance word returned has the object fred as its context. You can get the value it holds by using get:

print get in fred 'balance
$1000.00

The second argument to the in function is a literal word. This allows you to dynamically change words depending on what is needed:

words: [first-name last-name balance]
foreach word words [print get in fred word]
FredSmith
$1000.00

Each word in the block is used to obtain its value in the object.

The in function can also be used to set object variables.

set in fred 'balance $20.00
print fred/balance
$20.00

If a word is not defined within an object, the in function returns none . This is useful for detecting when a variable exists within an object.

if get in fred 'bank [print fred/bank]

5. Object Functions

An object can contain variables that refer to functions that are defined within the context of the object. This is useful because the functions are encapsulated within the context of the object, and can access the other variables of the object directly, without a need for a path.

As a simple example, the example object can include functions for computing new values within the object:

example: make object! [
    var1: 10
    var2: var1 + 10
    var3: now/time
    set-time: does [var3: now/time]
    calculate: func [value] [
        var1: value
        var2: value + 10
    ]
]

Notice in the example that the functions are able to refer to the variables of the object directly, rather than as paths. That is possible because the functions are defined within the same context as the variables they access.

To set a new time, use:

example/set-time

This example evaluates the function that sets var3 to the current time.

To calculate new values for var1 and var2 , use:

example/calculate 100
print example/var2
110

In the case of the bank-account object, the functions for deposit and withdraw can be added to the current definition:

bank-account: make bank-account [
    deposit: func [amount [money!]] [
        balance: balance + amount
    ]
    withdraw: func [amount [money!]] [
        either negative? balance [
            print ["Denied. Account overdrawn by"
                absolute balance]
        ][balance: balance - amount]
    ]
]

In the example, notice that the functions are able to refer to the balance directly within the object. That's because the functions are part of the object's context.

Now if a new account is made, it will contain functions for depositing and withdrawing money. For example:

lily: make-account "Lily" "Lakeswimmer" $1000

print lily/balance
$1010.00
lily/deposit $100

print lily/balance
$1110.00
lily/withdraw $2000

print lily/balance
-$890.00
lily/withdraw $2.10
Denied. Account overdrawn by $890.00

6. Prototype Objects

Any object can serve as a prototype for making new objects. For instance, the lily account object previously defined can be used to make new objects with a line such as:

maya: make lily []

This makes an instance of an object. The object is a copy of the customer object and has identical values:

print lily/balance
-$890.00
print maya/balance
-$890.00

You can modify the new object while making it by providing the new values within the definition block:

maya: make lily [
    first-name: "Maya"
    balance: $10000
]

print maya/balance
$10000.00
maya/deposit $500

print maya/balance
$10500.00
print maya/first-name
Maya

The lily object serves as a prototype for creating the new object. Any words that are not redefined for the new object continue to have the values of the old object:

print maya/last-name
Lakeswimmer

New words are added to the object in a similar way:

maya: make lily [
    email: maya@example.com
    birthdate: 4-July-1977
]

7. Referring to Self

Every object includes a predefined variable called self . Within the context of an object, the self variable refers to the object itself. It can be used to pass the object to other functions or to return it as a result of a function.

In the following example, the show-date function requires an object as its argument and self is passed to it:

show-date: func [obj] [print obj/date]

example: make object! [
    date: now
    show: does [show-date self]
]

example/show
16-Jul-2000/11:08:37-7:00

Another example of using the self variable is a function that clones itself:

person: make object! [
    name: days-old: none
    new: func [name' birthday] [
        make self [
            name: name'
            days-old: now/date - birthday
        ]
    ]
]

lulu: person/new "Lulu Ulu" 17-May-1980

print lulu/days-old
7366

8. Encapsulation

An object provides a good way to encapsulate a group of variables that should not appear at the global level. When function variables are defined as globals, they can unintentionally be modified by other functions.

The solution to this problem of global variables is to wrap an object around both the variables and the function. When that is done, the function can still access the variables, but the variables cannot be accessed globally. For example:

Bank: make object! [

    last-account: 89431
    bank-bonus: $10.00

    set 'make-account func [
        "Returns a new account object"
        f-name [string!] "First name"
        l-name [string!] "Last name"
        start-balance [money!] "Starting balance"
    ][
        last-account: last-account + 1
        make bank-account [
            first-name: f-name
            last-name: l-name
            account: last-account
            balance: start-balance + bank-bonus
        ]
    ]
]

In this example, the variables are safe from accidental modification. Notice that the make-account function was set to a variable using the set function, rather than using a variable definition. This was done to make it a global function. The function can be used in the same way as functions set with a variable definition, but does not require an object path:

bob: make-account "Bob" "Baker" $4000

9. Reflective Properties

As with many other REBOL data types, you can access the components of objects in a manner that allows you to write useful tools and utilities for creating, monitoring, and debugging them.

The first and second functions allow you to access the components of an object. The first function returns the words defined for an object. The second function returns the values that the objects are set to. The following diagram shows the relationship between the return values of first and second:

The advantage to using first is that it allows you to obtain a list of the words for the function without knowing anything else about the function:

probe first luke
[self first-name last-name account balance]

In the above example, notice that the list contains the word, self , which is a reference to the object itself. You can exclude self when getting an object's word list by using next:

probe next first luke
[first-name last-name account balance]

Now you have a way to write a function that can probe the contents of an object:

probe-object: func [object][
    foreach word next first object [
        print rejoin [word ":" tab get in object word]
    ]
]

probe-object fred
first-name: Luke
last-name: Lakeswimmer
account: 89431
balance: $1204.52

When accessing objects in this fashion, care should be taken to avoid infinite loops. For instance, if you attempt to probe certain objects that contain references to themselves, your code may begin an endless loop. This is the reason why you cannot probe the system object directly. The system object contains many references to itself.


Updated 8-Apr-2005 - Copyright REBOL Technologies - Formatted with MakeDoc2
REBOL.com Documents Manual Dictionary Library Feedback