chickadee » operations

operations

This extension implements the T object system.

This library supports the object-oriented style with a special kind of procedure known as an operation, and with forms which permit one to create objects which exhibit certain behavior when a given operation is applied to them.

When an operation is called, the following sequence of events occurs:

In this way, an object's handler may determine how the operation is to be performed for the object - that is, which particular method is to be invoked as a result of invoking the operation.

Handlers map operations to methods. Many objects may have the same handler, or a handler may be idiosyncratic to a particular object. However, every object has a handler, so all objects participate in the generic operation dispatch protocol.

Fundamental forms

The basis of the generic operation system consists of the two special forms OBJECT and OPERATION. OBJECT-expressions create objects which respond appropriately to generic operations, and OPERATION-expressions evaluate to operations.

(object procedure . method-clauses)syntax

Returns: object

An OBJECT-expression yields an object which is prepared to handle generic operations according to the method-clauses. In the following description, ``the object'' refers to the value of a given OBJECT-expression.

Each method-clause should be of the form

 ((operation . variables) . body)

Operation is an evaluated position, and is typically a variable which evaluates to an operation, although it may be any expression. When an operation is called with the object as its first argument, the operation-expressions are evaluated, and if one yields the operation being applied to the object, the corresponding method-clause is selected. The operation is then performed according to the selected method-clause: the clause's variables are bound to the arguments to the operation, and its body, an implicit block, is evaluated.

(define op (operation #f))
(op (object #f ((op self) 34)))      ==> 34
(op (object #f ((op self x) x)) 55)  ==> 55

Procedure may be any expression, and is evaluated at the time the OBJECT-expression is evaluated. The object, when called, simply calls the value of the procedure expression, passing on any arguments. Typically procedure might be either a LAMBDA-expression, if the object is to be callable, or it is #f, which by convention means that the object is not intended to be called, the value of #f being an uncallable object.

In the degenerate case, where there are no method clauses, the value of

 (object (lambda args . body))

is indistinguishable from that of

 (lambda args . body)

The semantics of the OBJECT and OPERATION special forms can be described in terms of hypothetical primitive procedures *OBJECT and GET-HANDLER. These primitives do not actually exist, but are introduced here as expository aids. *OBJECT takes two arguments, and returns an object which, when called, calls the object which was *OBJECT's first argument, and when given to GET-HANDLER returns the object which was *OBJECT's second argument. That is, *OBJECT creates a two-component record (like a pair), GET-HANDLER extracts one component, and the other component is called when the record is called.

 (get-handler (*object proc handler))   ==>  handler
 ((*object proc handler) arg ...)       ==>  (proc arg ...)

In addition, GET-HANDLER is defined on all objects to return some handler, even objects not created by *OBJECT (if indeed there are any such objects).

Given these primitives, the following rough equivalence holds:

 (object proc
       ((op1 . args1) . body1)
       ((op2 . args2) . body2)
       ...
       ((opn . argsn) . bodyn))
 ==>
 (*object proc
        (lambda (op)
          (switch op
            (op1 (lambda args1 . body1)) 
            (op2 (lambda args2 . body2)) 
            ...
            (opn (lambda argsn . bodyn))
            (else #f))))

The outer LAMBDA-expression yields the object's handler; the inner LAMBDA-expressions yield the methods, and the mapping from operations to methods is accomplished by the SWITCH-expression Note that the syntactic positions

 op1,
 op2,
 ...
 opN

are evaluated positions, and the operation expressions are evaluated when an operation is applied to the object, not when the object is created.

(operation default . method-clauses)syntax

Returns: operation

The syntax of OPERATION is the same as that of OBJECT, but its semantics and application are somewhat different. An OPERATION-expression evaluates to an operation. When called, the operation obtains a handler for its first argument, calls the handler to obtain a method, and then invokes the method. The default method for the operation is established as being default.

As the subject of another generic operation, an operation is an object like any other, and in this case the operation acts just as if it had been created by an OBJECT-expression with the same method-clauses. In this way one can establish the behavior of an operation when subject to other operations, for example SETTER.

The following rough equivalence describes the semantics of OPERATION. Some details have been omitted.

 (operation default . methods)
  ==>
 (labels ((op (object (lambda (obj . args)
                      (let ((method ((get-handler obj) op)))
                        (cond (method
                               (apply method obj args))
                              (else
                               (apply default obj args)))))
                    . methods)))
 op)

For example:

 (define op (operation (lambda (obj) 'zebu)))
 (op (object #f ((op self) 'quagga)))    -->  quagga
 (op 'eland)                              -->  zebu

An operation is created, and the variable OP is bound to it. The operation's default method always returns the symbol ZEBU. When the operation is applied to the value of the OBJECT-expression, the appropriate method is invoked, and the call to the operation yields the symbol QUAGGA. When the operation is applied to an object which doesn't handle it - the symbol ELAND - the operation's default method is invoked, so the call yields the symbol ZEBU.

operation? objectprocedure

Returns: boolean

Returns true if object is an operation.

Defining operations

(define-operation (variable . argument-vars) . body)syntax

Returns: undefined

Defines variable to be an operation. The syntax is intended to be analogous to that of DEFINE. The operation's default method is defined by argument-vars and body. If there is no body, then the operation's default method is undefined. In this case, the argument-vars appear only for documentary purposes.

 (define-operation (var . args) . body)
   ==> (define var (operation (lambda args . body)))
 (define-operation (var . args))
   ==> (define var (operation undefined-effect))
 [syntax] (define-settable-operation (variable . argument-vars) . body)   --> undefined

Defines variable to be an operation, as with DEFINE-OPERATION, but arranges for the operation's ``setter to be another operation, so that the operation is ``settable

 (define-settable-operation (var . args) . body)
   ==> 
 (define var
 (let ((the-setter (operation undefined-effect))) 
   (operation (lambda args . body) 
     ((setter self) the-setter))))
(define-predicate variable)syntax

Returns: undefined

Defines variable to be an operation which, by default, returns false.

 (define-predicate var) 
   ==>
 (define-operation (var obj) #f)

The intent is that particular OBJECT-expressions contain clauses of the form ((variable SELF) #t). This way the operation defined by DEFINE-PREDICATE may act as a type predicate that returns true only for those objects returned by such OBJECT-expressions.

Default operations

 [operation] (print-object OBJECT [PORT])

Writes a textual representation of OBJECT to PORT, which defaults to the value of (current-output-port).

Example

Hypothetical implementation of cons:

 (define-predicate pair?) 
 (define-settable-operation (car pair)) 
 (define-settable-operation (cdr pair)) 
 (define (cons the-car the-cdr) 
   (object #f 
         ((pair? self) #t) 
         ((car self) the-car) 
         ((cdr self) the-cdr) 
         (((setter car) self new-car) (set the-car new-car)) 
         (((setter cdr) self new-cdr) (set the-cdr new-cdr))))

Author

felix winkelmann

License

Copyright (c) 2007-2022, Felix L. Winkelmann
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

  Redistributions of source code must retain the above copyright notice,
    this list of conditions and the following disclaimer. 
  Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.
  Neither the name of the author nor the names of its contributors may be
    used to endorse or promote products derived from this software without
    specific prior written permission.
 
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

Version History

0.6
ported to CHICKEN 5
0.5
fix in define-operation
0.1
Initial release

Contents »