7. Bigloo -- Object System

Browsing
Home: | Bigloo A ``practical Scheme compiler'' User manual for version 2.6b December 2003
Previous chapter: | Pattern Matching
Next chapter: | Threads
Object System
7.1 | Class declaration
7.2 | Creating and accessing objects
7.3 | Generic functions
7.4 | Widening and shrinking
7.5 | Object library
| 7.5.1 | Classes handling
| 7.5.2 | Object handling
7.6 | Object serialization
7.7 | Equality
7.8 | Introspection
Chapters
| Acknowledgements
1. | Table of contents
2. | Overview of Bigloo
3. | Modules
4. | Core Language
5. | Standard Library
6. | Pattern Matching
7. | Object System
8. | Threads
9. | Regular parsing
10. | Lalr(1) parsing
11. | Errors and Assertions
12. | Eval and code interpretation
13. | Macro expansion
14. | Command Line Parsing
15. | Explicit typing
16. | The C interface
17. | The Java interface
18. | Bigloo Libraries
19. | Extending the Runtime System
20. | SRFIs
21. | DSSSL support
22. | Compiler description
23. | User Extensions
24. | Bigloo Development Environment
25. | Global Index
26. | Library Index
| Bibliography
| Bigloo's object system is designed to be as simple as possible and belongs to the Clos [Bobrow et al. 88] object system family in that it uses classes, generic functions and methods. Its design has been strongly influenced by C. Queinnec's Meroon [Queinnec93] It does not include any meta object protocol.
7.1 Class declaration
Classes are defined in a module declaration. If a class declaration takes place in a static module clause (see Section Module Declaration) its scope is limited to the current module but if it takes place in an export module clause, its scope is extended to all modules that import the current module. The syntax of a class declaration is:
    <class> ==> (class <ident> <constructor>? <field>+)
         | (final-class <ident> <constructor>? <field>+)
         | (wide-class <ident> <constructor>? <field>+)
         | (abstract-class <ident> <constructor>? <field>+)
    <constructor> ==> ( <expr> )
    <field> ==> <ident>
         | (* <ident>)
         | (<ident> <field-prop>)
         | (* <ident> <field-prop>)
    <len> ==> <fixnum> | <string>
    <field-prop> ==> read-only
         | (get <bigloo-exp>)
         | (set <bigloo-exp>)
         | (default <bigloo-exp>)
         | (info <bigloo-exp>)

A class is a Bigloo type and the class identifier is extracted from the <ident> of the class definition. If <ident> is also a <typed-ident>, the type part of this identifier denote the super-class of the class. If <ident> is a <Ieee-ident>, the super-class of the class is the root of the inheritance tree, the `object` class. This `object` class is the only pre-existing class.
Final classes can only be sub-classed by wide classes. Wide classes can only inherit from final classes. abstract classes can't be instantiated.
The optional constructor is an expression that must evaluate to a one argument function. This function is automatically invoked each time a new class instance is created. The constructor is passed the fresh instance. If a class is not defined a constructor the super class constructors are searched. The first constructor found is invoked. A constructor may be a generic function with a method specified for one or more classes.
A class field may be a typed class field which is achieved by using a <typed-ident> instead of a <Ieee-ident> for the <ident> value.
A class field defined as `(* ...)` is an indexed field and holds a sequence of items. The length of this sequence is defined at allocation time.
Field marked with `read-only` declaration are immutables.
`Default` declarations allow default field values. For indexed fields, the default value is the default value of each element of the sequence.
For the means of an example, the traditional points and colored points can be defined as:

    (module example
       (static (abstract-class pt)
               (class point::pt
                  x::double
                  y::double)
               (class point-C::point
                  (color::string read-only))))

We illustrate indexed fields, final and wide classes by the example:

    (module example
       (export (final-class person
                   (name::string (default "Jones"))
                   (sex read-only)
                   (* children::person))
               (wide-class married-person::person
                   mate::person)))

Fields may be virtual. A field is virtual as soon as its declaration contain a `get` attribute. Virtual fields have no physical implementation within the instance. When defining a virtual field, the class declaration implements a getter and a setter if that field is not a read only field. Access to the virtual field will relies on invocation to the user getter and user setter. For instance:

    (module example
       (static (class complex
                  mag::double
                  angle::double
                  (real::double (get (lambda (p)
                                        (with-access::complex p (mag angle)
                                           (* mag (cos angle)))))
                                read-only)
                  (imag::double (get (lambda (p)
                                        (with-access::complex p (mag angle)
                                           (* mag (sin angle)))))
                                read-only))))

    (let ((p (instantiate::complex (mag 1.0) (angle 2.18))))
       (with-access::complex p (real imag)
          (print "real: " real)
          (print "imag: " imag)))

Virtual fields cannot be associated default values. Virtual fields may not be indexed fields. If a virtual field is not provided with a setter it must be annotated as read only.
`Info` declarations allow arbitrary user information field values. This value can be retrieved by introspection, by the means of the `class-field-info` introspection function.
For the means of an example, with add to information to the slot of the point class.

    (module example
       (static (class point
                  (x::double (info '(range 0.0 10.0)))
                  (y::double (info '(range -1.0 1.0)))))

7.2 Creating and accessing objects

Bigloo automatically creates functions to allocate and manipulate objects. Let us suppose the class declaration:

    (class class
       field1::type1
       (* field2::type2))

Theses functions are:

- A creator:

      (make-class::class ::type1 ::long ::type2 ::type3)

  This function allocates a fresh instance of `class` and fills the field values with the argument. The length of the indexed field `field2` is given as argument of the function.
  Example:

      (class truc
         x::char
         (* y::bstring))

  The prototype of the allocation function is:

      (make-truc::truc x::char
                       y-len::long
                       y::bstring)

  The indexed values of the `y` and `z` fields will be set to the values of the formal parameters `y` and `z` respectively.
- A filler:

      (fill-class! ::class ::type1 ::long ::type2 ::type3)

  This function accepts the same arguments as the creator augmented with one allocated instance. This instance is filled up with the provided values.
- A type checker:

      (class?::bool obj::obj)

  This function returns `#t` if `obj` is an instance of `class` or an instance of a sub-class of `class`, otherwise, it returns `#f`.
- Accessors and mutators:

      (class-field_1::type_1 ::class)
      (class-field_1-set!::obj ::class ::type_1)
      (class-field_2-len::long ::class)
      (class-field_2-ref::type_2 ::class ::long)
      (class-field_2-set!::obj ::class ::long ::type_2)

  Declaring a field `read-only` prevents Bigloo from generating an associated ``class`-`field_i`-set!` function. The length of the indexed field is `read-only`. Thus they do not have mutator functions.

Some syntax helps with allocation and access to objects.

with-access::`class` obj (ident...) body | bigloo syntax
A reference to any of the variables named `ident` is replaced by the appropriate field access form. This is true for both reference and assignment. For instance:
    (with-access::point p (x y)
       (set! x (- x))
       (set! y (- y)))
    is equivalent to:
    (begin
       (point-x-set! p (- (point-x p)))
       (point-y-set! p (- (point-y p))))
When an indexed field is referenced inside the binding part of a `with-access` form, the functions that accesses and changes the fields are bound inside the body of the `with-access`. In addition, a variable reporting the length of the field is also bound. For a field `fd`, the getter function is `(`fd`-ref `offset`)`, the setter is `(`fd`-set! `offset` `value`)`, the length variable is ``fd`-len`. For instance:
    (with-access::truc o (y)
       (print "length: " y-len)
       (print "old y-ref(0): " (y-ref 0))
       (y-set! 0 "foo")
       (print "new y-ref(0): " (y-ref 0)))
    is equivalent to:
    (begin
       (print "length: " (truc-y-len o))
       (print "old y-ref(0): " (truc-y-ref o 0))
       (truc-y-set! o 0 "foo")
       (print "new y-ref(0): " (truc-y-ref o 0)))

instantiate::`class` (ident value)... | bigloo syntax
This forms allocates object of class `class` and fills the fields with values found in the list of parameters (note that field are explicitly named and that there is no ordering for field naming). Field values which are not provided in the parameter list must have been declared with a `default` value which is used to initialize the corresponding field. For instance:
    (make-point 0 0)
is equivalent to:
    (instantiate::point (x 0) (y 0))
Indexed field must be provided with exactly two values. The first is the length of the indexed field and the second is the default value of each element of the indexed field.

co-instantiate ((`var` `value`) ...) body | bigloo syntax
This form permits the creation of recursive instances. It is specially useful for creating instances for which class declarations contain cyclic type references (for instance a class `c1` for a which a field is declared of class `c2` and a class `c2` for which a class is declared of type `c1`). The syntax of a `co-instantiate` form is similar to a `let` form. However the only legal `values` are `instantiate` forms. The variables introduced in the binding of a `co-instantiate` form are bound in `body`. In addition, they are partially bound in the `values` expressions. In a `value` position, a variable `var` can only be used to set the value of a field of an instantiated class. It cannot be used in any calculus. Example:
    (module obj-example
       (export (class c1 a b o2::c2)
               (class c2 x y o1::c1)))

    (co-instantiate ((o1 (instantiate::c1
                            (a 10)
                            (b 20)
                            (o2 o2)))
                     (o2 (instantiate::c2
                            (x 10)
                            (y 20)
                            (o1 o1))))
       (+ (c2-x (c1-o2 o1)) (c2-y (c1-o2 x))))
                                           => 30

duplicate::`class` obj (ident value)... | bigloo syntax
This forms allocates an instance of class `class`. The field values of the new object are picked up from the field values of the `old` object unless they are explicitly given in the parameter list. For instance:
    (with-access::point old (x)
       (make-point x 10))
is equivalent to:
    (duplicate::point old (y 10))
Indexed field must be provided with exactly two values. The first is the length of the indexed field and the second is the default value of each element of the indexed field. Here is an example of creations and mutations of a complex object:
    (module foo
       (export (class named-tab
                  name::string
                  (* els::int))))

    ;; a function than print a named-tab
    (define (print-tab tab)
       (display* (named-tab-name tab) ": ")
       (let loop ((i (-fx (named-tab-els-len tab) 1)))
          (if (=fx i -1)
              (newline)
              (begin
                 (display* #\( i
                           " "
                           (named-tab-els-ref tab i)
                           ") ")
                 (loop (-fx i 1))))))

    ;; we allocate a named-tab object with
    ;; 5 els, each of them initialized to 0
    (define a-tab (instantiate::named-tab
                     (name "example")
                     (els 5 0)))

    ;; we print its elements
    (print-tab a-tab)

    ;; we change the values of the indexed field
    ;; els by setting values from 0 to 10
    (let loop ((i (-fx (named-tab-els-len a-tab) 1)))
       (if (=fx i -1)
           'done
           (begin
              (named-tab-els-set! a-tab i i)
              (loop (-fx i 1)))))

    ;; we re-print it
    (print-tab a-tab)
This will produce the following output:
    example: (4 0) (3 0) (2 0) (1 0) (0 0)
    example: (4 4) (3 3) (2 2) (1 1) (0 0)

7.3 Generic functions

A generic function is a bag of specific functions known as methods. When invoked on a Bigloo object, a generic function determines the class of the discriminating variable (corresponding to the first argument of the generic function) and invokes the appropriate method. Generic functions implement single inheritance and each is defined using the `define-generic` Bigloo syntax.

define-generic (name arg...) default-body | bigloo syntax
A generic function can be defined with a default body which will be evaluated if no method can be found for the discriminating variable. The default default-body signals an error.

As an example, here is a possible definition of the `object-display` generic function:

    (define-generic (object-display obj::object . op)
       (let ((port (if (pair? op)
                       (car op)
                       (current-output-port))))
          (display "#\|" port)
          (display (class-name (object-class obj)) port)
          (display "\|" port)))

Methods can be defined to specialize a generic function and such methods must have a compatible variable list. That is, the first argument of the method must be a sub-type (i.e. belong to a sub-class) of the first argument of the generic function. Other formal parameters must be of same types. Moreover, the result type of the method must be a sub-type of the result of the generic function.

define-method (name arg...) body | bigloo syntax
If there is no appropriate method, an error is signaled. Methods can use the form `(call-next-method)` to invoke the method that would have been called if not present. The `(call-next-method)` cannot be used out of method definition.

example:

    (define-method (object-display p::person . op)
       (let ((port (if (pair? op)
                       (car op)
                       (current-output-port))))
          (fprint p "firstname : " (person-fname p))
          (fprint p "name      : " (person-name p))
          (fprint p "sex       : " (person-sex p))
          p))

7.4 Widening and shrinking

Bigloo introduces a new kind of inheritance: widening. This allows an object to be temporarily widened (that is transformed into an object of another class, a wide-class) and then shrink-ed (that is reshaped to its original class). This mechanism is very useful for implementing short-term data storage. For instance, Bigloo compilation passes are implemented using the widening/shrinking mechanism. On entry to a pass, objects are widened with the specific pass fields and, on exit from a pass, objects are shrunk in order to forget the information related to this pass.
Only instances of final classes can be widened and objects can only be widened in order to become instances of wide classes. Widening is performed by the `widen!` syntax:

widen!::`wide-class` obj (id value) ... | bigloo syntax
The object `obj` is widened to be instance of the wide class `wide-class`. Fields values are either picked up from the parameter list of the `widen!` form or from the default values in the declaration of the wide class.

Objects are shrunk using the `shrink!` syntax:

shrink! obj | bigloo syntax

Here is a first example:

    (module example
       (static (final-class point
                  (x (default 0))
                  (y (default 0)))
               (wide-class named-point::point name)))

    (define *point* (instantiate::point))

Two classes have been declared and an instance `*point*` of `point` has been allocated. For now, `*point*` is an instance of `point` but not an instance of `named-point` and this can be checked by:

    (print (named? *point*))           ==> #t
    (print (named-point? *point*))     ==> #f

Now, we widen `*point*`...

    (let ((n-point (widen!::named-point *point*
                      (name "orig"))))

And we check that now, `n-point` is an instance of `named-point`. Since `named-point` is a subclass of `point`, `n-point` still is an instance of `point`.

    (print (named-point? n-point))  ==> #t
    (print (named? n-point))        ==> #t

Widening affects the objects themselves. It does not operate any copy operation. Hence, `*point*` and `n-point` are `eq?`.

    (print (eq? n-point *point*))   ==> #t

To end this example, we shrink `n-point` and check its class.

    (shrink! n-point)
    (print (named-point? *point*))) ==> #f

Here is a more complex example:
We illustrate widening and shrinking using our ``wedding simulator''. First let us define three classes, `person` (for man and woman), `married-woman` and `married-man`:

    (module wedding
       (static (final-class person
                   name::string
                   fname::string
                   (sex::symbol read-only))
               (wide-class married-man::person
                   mate::person)
               (wide-class married-woman::person
                   maiden-name::string
                   mate::person)))

As we can see people are allowed to change their name but not their sex.
The identity of a person can be printed as

    (define-method (object-display p::person . op)
       (with-access::person p (name fname sex)
          (print "firstname : " fname)
          (print "name      : " name)
          (print "sex       : " sex)
          p))

A married woman's identity is printed by (we suppose an equivalent method definition for married-man)

    (define-method (object-display p::married-woman . op)
       (with-access::married-woman p (name fname sex mate)
          (call-next-method)
          (print "married to: " (person-fname mate)
                                " "
                                (person-name mate))
          p))

We create a person with the `birth` function:

    (define (birth name::string fname::string sex)
       [assert (sex) (memq sex '(male female))]
       (instantiate::person
          (name name)
          (fname fname)
          (sex sex)))

We celebrate a wedding using the `get-married!` function:

    (define (get-married! woman::person man::person)
       (if (not (and (eq? (person-sex woman) 'female)
                     (eq? (person-sex man) 'male)))
           (error "get-married"
                  "Illegal wedding"
                  (cons woman man))
           (let* ((mname (person-name woman))
                  (wife  (widen!::married-woman woman
                          (maiden-name mname)
                          (mate man))))
              (person-name-set! wife (person-name man))
              (widen!::married-man man
                 (mate woman)))))

We can check if two people are married by

    (define (couple? woman::person man::person)
       (and (married-woman? woman)
            (married-man? man)
            (eq? (married-woman-mate woman) man)
            (eq? (married-man-mate man) woman)))

Now let us study the life a `Junior` `Jones` and `Pamela` `Smith`. Once upon a time...

    (define *junior* (birth "Jones" "Junior" 'male))
    (define *pamela* (birth "Smith" "Pamela" 'female))

Later on, they met each other and ... they got married:

    (define *old-boy-junior* *junior*)
    (define *old-girl-pamela* *pamela*)
    (get-married! *pamela* *junior*)

This union can be checked:

    (couple? *pamela* *junior*)
       => #t

We can look at the new identity of `*pamela*`

    (print *pamela*)
       -| name      : Jones
          firstname : Pamela
          sex       : FEMALE
          married to: Junior Jones

But `*pamela*` and `*junior*` still are the same persons:

    (print (eq? *old-boy-junior* *junior*))   => #t
    (print (eq? *old-girl-pamela* *pamela*))  => #t

Unfortunately all days are not happy days. After having been married `*pamela*` and `*junior*` have divorced:

    (define (divorce! woman::person man::person)
       (if (not (couple? woman man))
           (error "divorce!"
                  "Illegal divorce"
                  (cons woman man))
           (let ((mname (married-woman-maiden-name
                          woman)))
              (begin
                 (shrink! woman)
                 (person-name-set! woman mname))
              (shrink! man))))

    (divorce! *pamela* *junior*)

We can look at the new identity of `*pamela*`

    (print *pamela*)
       -| name      : Smith
          firstname : Pamela
          sex       : FEMALE

And `*pamela*` and `*junior*` still are the same persons:

    (print (eq? *old-boy-junior* *junior*))   => #t
    (print (eq? *old-girl-pamela* *pamela*))  => #t

7.5 Object library

### 7.5.1 Classes handling

No type denotes Bigloo's classes. These objects are handled by the following library functions:

class? obj | bigloo procedure
Returns `#t` if and only if `obj` is a class.

class-super class | bigloo procedure
Returns the super-class of `class`.

class-subclasses class | bigloo procedure
Returns the subclasses of `class`.

class-name class | bigloo procedure
Returns the name (a symbol) of `class`.

object-constructor class | bigloo procedure
Returns `class`'s constructor.

object-class object | bigloo procedure
Returns the class that `object` belongs to.

### 7.5.2 Object handling

wide-object? object | bigloo procedure
Returns `#t` if `object` is a wide object otherwise it returns `#f`.

object-display object [port] | bigloo generic
This generic function is invoked by `display` to display objects.

object-write object [port] | bigloo generic
This generic function is invoked by `write` to write objects.

object->struct object | bigloo generic
struct->object struct | bigloo procedure
These functions converts objects into Scheme structures and vice-versa.

object-equal? object obj | bigloo generic
This generic function is invoked by `equal?` when the first argument is an instance of `object`.

object-hashnumber object | bigloo generic
This generic function returns an hash number of `object`.

is-a? obj class | bigloo procedure
Returns `#t` if `obj` belongs to `class` otherwise it returns `#f`.

7.6 Object serialization

Objects can be serialized and un-serialized using the regular `string->obj` and `obj->string` functions. Objects can be stored on disk and restored from disk by the use of the `output-obj` and `input-obj` functions.

7.7 Equality

Two objects can be compared with the `equal?` function. Two object are equal if and only if they belong to a same class, all their field values are equal and all their super class's field values are equal.

7.8 Introspection

Bigloo provides the programmer with some object introspection facilities. See section see Object library for information on classes and objects handling. Introspection facilities are, by default, available for all classes. However, in order to shrink the code size generation, it may be useful to disable class introspection. This decision can be taken on a per class basis (i.e., one class may be provided with introspection facilities while another one is not). The compiler option `-fno-reflection` (see Chapter Compiler Description) prevents the compiler to generate the code required for introspecting the classes defined in the compiled module.

class-fields class | bigloo procedure
Returns the a description of the fields of `class`. This description is a list of field descriptions where each field description can be accessed by the means of the following library functions. The fields are those directly defined in `class`. That is `class-fields` does not return fields defined in super classes of `class`.

class-all-fields class | bigloo procedure
Returns the a description of the fields of `class`. This description is a list of field descriptions where each field description can be accessed by the means of the following library functions. By contrast with `class-fields`, this function returns fields that are also defined in the super classes of `class`. in th

find-class-field class symbol | bigloo procedure
Returns the field named `symbol` from class `class`. Returns `#f` is such a field does not exist.

class-field? obj | bigloo procedure
Returns #t if `obj` is a class field descriptor. Otherwise returns #f.

class-field-name field | bigloo procedure
Returns the name of the `field`. The name is a symbol.

class-field-indexed? field | bigloo procedure
Returns `#t` if the described field is indexed and `#f` otherwise.

class-field-accessor field | bigloo procedure
Returns a procedure of one argument. Applying this function to an object returns the value of the field described by `field`.

class-field-len-accessor field | bigloo procedure
A one argument function is returned. Applying this function to an object returns the length value of the field described by field. It is an error to apply `class-field-len-accessor` to a non-indexed field.

class-field-mutable? field | bigloo procedure
Returns `#t` if the described field is mutable and `#f` otherwise.

class-field-mutator field | bigloo procedure
Returns a procedure of three, or two, arguments depending on whether the field is indexed or not. Applying this function to an object changes the value of the field described by `field`. It is an error to apply `class-field-mutator` to an immutable field.

class-field-info field | bigloo procedure
Returns the information associated to `field` (this the class declaration `info` attribute).

For means of an example, here is a possible implementation of the `equal?` test for objects:

    (define (object-equal? obj1 obj2)
       (define (class-field-equal? fd)
          (let ((get-value (class-field-accessor fd)))
             (if (not (class-field-indexed? fd))
                 (equal? (get-value obj1)
                         (get-value obj2))
                 (let* ((len (class-field-len-accessor
                              fd)))
                        (len1 (len obj1))
                        (len2 (len obj2)))
                    (and (=fx len1 len2)
                         (let loop ((i 0))
                            (cond
                               ((=fx i len1)
                                #t)
                               ((equal?
                                 (get-value obj1 i)
                                 (get-value obj2 i))
                                (loop (+fx i 1)))
                               (else
                                #f))))))))
       (let ((class1 (object-class obj1))
             (class2 (object-class obj2)))
          (cond
             ((not (eq? class1 class2))
              #f)
             (else
              (let loop ((fields (class-fields class1))
                         (class  class1))
                 (cond
                    ((null? fields)
                     (let ((super (class-super class)))
                        (if (class? super)
                            (loop (class-fields super)
                                  super)
                            #t)))
                    ((class-field-equal? (car fields))
                     (loop (cdr fields) class))
                    (else
                     #f)))))))

class-creator class | bigloo procedure
Returns the creator for `class`. The creator is a function for which the arity depends on the number of slots the class provides (see Section see Creating and accessing objects). When an instance is allocated by the means of the `class-creator`, as for direct instantiation, the class constructor is automatically invoked. Example:
    (module foo
       (main main)
       (static (class c1 (c1-constructor))))

    (define c1-constructor
       (let ((count 0))
          (lambda (inst)
             (set! count (+ 1 count))
             (print "creating instance: " count)
             inst)))

    (define (main argv)
       (let ((o1 (instantiate::c1))
             (o2 (instantiate::c1))
             (o3 ((class-creator c1))))
          'done))
       -| creating instance: 1
          creating instance: 2
          creating instance: 3
For each indexed field, two values must be sent to the function returned by `class-creator`. The first value denotes the size of the indexed field. The second value denote the initial values of the fields. For instance:
    (module example
       (export (class point x y (* z))))

    (let ((create (class-creator point)))
       (print (create 10 23 5 3)))
Produces -| #|POINT [X: 10] [Y: 23] [Z: 3 3 3 3 3]|

This Scribe page has been generated by `scribeinfo`.
Last update Tue Dec 16 13:46:41 2003