Roos OO System
See also roos/class, the interoperability macros for roos and racket/class.
1 Class Definition Syntax
syntax
(def-roos (class-name ...) this (supers ...) body ...)
this is bound to the object under construction. supers refers to instantiated superclass objects.
Each body entry may be:
Standard attribute: (attr val) — creates getter attr and setter attr!.
Persistent attribute: (persist "Doc" (attr val)) — also stored/restored via storage backend.
Documented attribute: ("Doc" (attr val)) — adds inline documentation to attribute.
Method: ((method args ...) expr ...) — defines a public method.
Documented method: ("Doc" ((method args ...) expr ...)) — with documentation.
Optional: you may define (init expr ...) to run code immediately after object construction.
Optional: you may define (finalize expr ...) to run cleanup logic when object is collected.
Methods and fields are always virtual. Superclass definitions are resolved based on declared order. Multiple inheritance is supported and left-to-right linearized.
def-roos supports default values, optional documentation, and user-defined persistence.
2 Object and Method Use
(-> obj field) — call getter for field.
(-> obj field! val) — set field.
(-> obj method args ...) — invoke method.
(->> obj name) — retrieve method/field procedure.
(roos-object? x) — is it a ROOS object?
(roos-class? x) — is it a ROOS class definition?
(roos-classname obj) — symbolic class name.
(roos-class obj) — class definition.
(roos-id obj) — unique object ID.
(roos-id! obj id) — set object’s ID (used in persistence).
2.1 Provided procedures
procedure
(-> obj name ...) → any/c
obj : any/c name : symbol?
procedure
(->> obj name) → procedure?
obj : any/c name : symbol?
procedure
(roos-class? x) → boolean?
x : any/c
procedure
(roos-object? x) → boolean?
x : any/c
procedure
(roos-obj? x) → boolean?
x : any/c
procedure
(roos-class obj) → any/c
obj : any/c
procedure
(roos-classname obj) → symbol?
obj : any/c
procedure
(roos-id obj) → symbol?
obj : any/c
procedure
(roos-id! obj id) → void?
obj : any/c id : symbol?
procedure
(roos-new class args ...) → any/c
class : any/c args : any/c
procedure
(-! class args ...) → any/c
class : any/c args : any/c
procedure
(roos-help) → void?
procedure
(with-roos-obj x) → any/c
x : any/c
procedure
(roos-storage! getter setter deleter stop-deleting!) → void? getter : procedure? setter : procedure? deleter : procedure? stop-deleting! : procedure?
procedure
(roos-storage-stop-deleting! flag) → void?
flag : boolean?
3 Persistence and Storage Backend
ROOS lets you persist selected attributes by tagging them with persist. Persistence is handled by user-provided backends through:
(roos-storage! getter setter deleter stop-deleting!)
Each function takes a ROOS object and field name:
getter obj field default — a function that returns stored value or default.
setter obj field val — a function that stores value.
deleter obj — a function that removes an object, i.e. all persistent fields for that (unless stop-deleting is #t).]
stop-deleting! #t — disables or enables deletion for current session.
See the full SQLite example in the next section.
3.1 Example of persistence backend for roos
Below is an example SQLite backend implementation that stores fields in a table:
(require db) (require racket/vector) ; ; Conversion of field values ; (define (value->string s-expr) (let ((o (open-output-string))) (write s-expr o) (get-output-string o))) (define (string->value str) (let ((o (open-input-string str))) (read o))) ; ; Database storage backend ; (define conn (sqlite3-connect #:database "roos.db" #:mode 'create)) (query-exec conn "CREATE TABLE IF NOT EXISTS store (class TEXT, id TEXT, field TEXT, val TEXT)") (define stop-deleting? #f) (define (stop-deleting-fn flag) (set! stop-deleting? flag)) (define (getter obj field default) (let ((class (symbol-string (roos-classname obj))) (id (symbol->string (roos-id obj))) (field (symbol->string field))) (let ((count (query-value conn "SELECT count(*) FROM store WHERE class=? AND id=? AND field=?" class id field))) (if (= count 0) default (let ((row (query-row conn "SELECT val FROM store WHERE class=? AND id=? AND field=?" class id field))) (if row (string->value (vector-ref row 0)) default)))))) (define (setter obj field val) (let ((class (symbol->string (roos-classname obj))) (id (symbol->string (roos-id obj))) (fld (symbol->string field)) (vstr (value->string val))) (query-exec conn "DELETE FROM store WHERE class=? AND id=? AND field=?" class id fld) (query-exec conn "INSERT INTO store (class, id, field, val) VALUES (?, ?, ?, ?)" class id fld vstr))) (define (deleter obj) (unless stop-deleting? (let ((class (symbol->string (roos-classname obj))) (id (symbol->string (roos-id obj)))) (query-exec conn "DELETE FROM store WHERE class=? AND id=?" class id)))) (roos-storage! getter setter deleter stop-deleting-fn) (plumber-add-flush! (current-plumber) (lambda (x) (printf "Collecting garbage to cleanup the storage for variables that have been cleared\n") (collect-garbage)))
3.2 Address Book Example with Persistent Vector of Person IDs
This example builds an address book with persistent reference to persons, using ROOS’ object ID mechanism.
(require racket/vector) ; person class (def-roos (person) this (supers) (persist (name "")) (persist (tel ""))) ; book class (def-roos (book) this (supers) (persist (ids (list))) (persist (name "")) ("" (persons (make-vector 0))) ((add p) (set! persons (vector-extend persons (+ (vector-length persons) 1) p)) (-> this ids! (vector->list (vector-map (lambda (o) (-> o roos-id)) persons)))) ((remove i) (set! persons (vector-append (vector-take persons i) (vector-drop persons (+ i 1)))) (-> this ids! (vector->list (vector-map (lambda (o) (-> o roos-id)) persons)))) ((for-each f) (letrec ((g (lambda (i n) (when (< i n) (f (vector-ref persons i)) (g (+ i 1) n))))) (g 0 (vector-length persons)))) (init (begin (-> this roos-id! 'book) (let ((ps (map (lambda (id) (let ((p (roos-new person))) (-> p roos-id! id) p)) (-> this ids)))) (set! persons (list->vector ps)))))) ; Create sample data (define b (-! book)) (define (adder n t) (let ((p (-! person))) (-> p name! n) (-> p tel! t) (-> b add p))) (adder "Alice" "123") (adder "Bob" "456") (adder "Jos" "982") (adder "Rebecca" "363") (-> b (for-each (lambda (p) (displayln (-> p name))))) ; Reopen addressbook later from persistent storage (define a (-! book)) (-> b (for-each (lambda (p) (displayln (-> p name)))))
Note: call (roos-storage-stop-deleting! #t) before shutdown to prevent finalizers from purging storage content.
4 Cyclic References and Garbage Collection
ROOS objects can reference each other freely, including circular (cyclic) references. For example, a doubly-linked list:
(def-roos (node) this (supers) (persist "Value" (val 0)) (next #f) (prev #f)) (define a (-! node)) (-> a val! 1) (define b (-! node)) (-> b val! 2) (-> a next! b) (-> b prev! a)
To avoid resource leaks when such cyclic structures are finalized, make sure that any cleanup (e.g. persistence flush) is done in finalize methods. Racket’s garbage collector can collect cyclic references if there are no external references left.
If persistent fields depend on each other cyclically (e.g. mutual IDs), you may want to:
Assign fixed IDs at creation time.
Defer construction of cyclic pointers until after all involved objects exist.
Use init to resolve and wire up these references after restoring from persistent state.
Cyclic references are supported and safe as long as your finalization logic handles them properly.