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.
Classes are defined in a module declaration. A class declaration can
take place in a compiled or interpreted module. 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 ident field ... | bigloo module clause |
<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 (only for compiled modules) can only inherit from final classes.
abstract classes can't be instantiated.
Wide-classes cannot be defined within the interpreter.
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 has 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
rely on invocation of 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)))))
|
|
9.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 nil instance:
This function returns the NIL pre-existing class instance. This instance plays
the role of
void *
in C or null
in Java. The value of each field
is unspecified but correct with respect to the Bigloo type system. Each
call to class
-nil
returns the same object (in the sense of
eq?
).
- 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:
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 (binding...) body | bigloo syntax |
A reference to any of the variables defined in as a binding is
replaced by the appropriate field access form. This is true for both
reference and assignment. A binding is either a symbol or a list
of two symbols. In the first place, it denotes a field. In the second
case, it denotes an aliases field.
For instance:
(with-access::point p (x (y1 y))
(set! x (- x))
(set! y1 (- y1)))
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:
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 is only available from compiled modules. In other words, it
is not available from the interpreter. It 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 that prints 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 4
(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)
|
|
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))
|
|
9.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:
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
|
No type denotes Bigloo's classes. These objects are handled
by the following library functions:
find-class symbol | bigloo procedure |
Returns, if any, the class named symbol .
|
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.
|
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 .
|
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.
In addition to this standard serialization mechanism, custom object
serializers and un-serializers can be specified by the means of the
register-class-serialization!
function (see Section
Serialization.
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.
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 denotes 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]|
|
class-nil class | bigloo procedure |
Returns the NIL creator for class .
|
class-predicate class | bigloo procedure |
Returns the predicate for class . This predicate returns #t
when applied to object of type class . It returns #f otherwise.
|