Scheme object systems: POS
I’m no OOP fan (much less a fan of single-dispatch OOP), but sometimes I miss the implicit lexical scope that single-dispatch provides for methods. Take something as simple as
class Rect {
int top, left, bottom, right;
int area () const {
return (top - bottom) * (right - left);
}
Most Scheme object systems (see for example the Chicken OOP section) turn the area() method body into something tedious along the lines of
(* (- (slot-ref self 'top) (slot-ref self 'bottom))
(- (slot-ref self 'right) (slot-ref self 'left)))
or, with objects implemented as closures,
(* (- (self 'top) (self 'bottom) ...) ...)
Until recently, I thought that short of codewalker-based macros, nothing could restore the terseness of single-dispatch methods.
Well, I’ve discovered Blake McBride’s POS (portable object system). With POS, you can write
(define-class Rect (ivars top left bottom right)
(imeths
(set-top! (self val) (set! top val)) ...
(get-area (self) (* (- top bottom) (- right left)))))
POS is a set of pure R5RS macros, and correctly interacts with other syntax-rules macros (e.g. macros can appear within method bodies). The trick is not only to represent the objects as closures, but to also expand method bodies inside the closure:
;; not the actual POS expansion -- just an illustration
(define (make-rect)
(let ((top #f) (left #f) (bottom #f) (right #f))
(define self
(lambda (meth-name . args)
(case meth-name
((set-top!)
(apply (lambda (self val) (set! top val))
(cons self args))) ...
((get-area)
(apply (lambda (self)
(* (- top bottom) (- right left))
(cons self args)))))))
self))
This way, methods can access instance variables as simple literals. Each object is a dispatch function that closes over those variables.
POS is very useful, and I plan to add default getters and setters, as well as a way to convert between the closure representation and a-lists. This should help with persistence, among other things.
POS has a couple of extra features (inheritance, access to the parent object, class methods) but really is a light-weight system. The major downside is that methods (and instance variables) can no longer be added dynamically, since it’s impossible to inject code (or data) into a closure.
Update: see the comments for yet another way of simulating an implicit “this” argument.
