[plt-scheme] Steps Towards Stateless State - Commented

From: Zelah Hutchinson (zelah at inbox.com)
Date: Sun Jun 28 04:18:37 EDT 2009

(define (append-symbol sym1 sym2)
  (let ((str1 (symbol->string sym1))
        (str2 (symbol->string sym2)))
    (string->symbol (string-append str1 str2))))
;used for constructing fully qualified method names

(define -self- car)
(define -state- cadr)
(define -arguments- caddr)
;used internally to methods

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (->object . st)
  (lambda (method-name . args)
    (apply (eval (append-symbol '->object method-name))
           (let ((self '->object)
                 (state (if (null? st)
                            (list)
                            st))
                 (arguments (car args)))
             (list self state arguments)))))
;this class def also constructs objects of type ->object

(define (->object:state . internals)
  (-state- internals))
;:state method returns the state variables of an object

(define (->object:display . internals)
  (cons (-self- internals) (-state- internals)))
;:display method is like :state method but prefixed with the appropriate class name

(define (->object:set . internals)
  (define (item-q state item)
    (if (null? state)
        #f
        (if (equal? (car state) (car item))
            (list (car state) (cadr state))
            (item-q (cddr state) item))))
  (define (remove-matching state item)
    (if (equal? (car state) (car item))
        (cddr state)
        (cons (car state)
              (cons (cadr state)
                    (remove-matching (cddr state) item)))))
  (define (add1 item state)
    (let ((found (item-q state item)))
      (if found
          (append item (remove-matching state item))
          (append item state))))
  (define (update state arguments)
    (if (null? arguments)
        state
        (update
         (add1 (list (car arguments) (cadr arguments)) state)
         (cddr arguments))))
  (let ((state (-state- internals))
        (arguments (-arguments- internals)))
    (apply ->object (update state arguments))))
;:set method returns a new object of the ->object class

(define (->object:datum . internals)
  (define (item-q state item)
    (if (null? state)
        #f
        (if (equal? (car state) (car item))
            (list (car state) (cadr state))
            (item-q (cddr state) item))))
  (let ((state (-state- internals))
        (arguments (-arguments- internals)))
    (let ((item (append arguments '(#f))))
      (cadr (item-q state item)))))
;:datum method returns a value from the internal state when given its associated
identifier
;very easy to add more methods without modification to the above code
;just add a boiler plate definition at the bottom for any new methods added to the system
;notice that because of message passing we get polymorphism automatically

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (->history . st)
  (lambda (method-name . args)
    (apply (eval (append-symbol '->history method-name))
           (let ((self '->history)
                 (state (if (null? st)
                            (list 'H= 0)
                            st))
                 (arguments (car args)))
             (list self state arguments)))))
;the ->history class is basically the same
;just substituted the new name and added a default state when constructed with no
arguments

(define ->history:state ->object:state)
;inherits method from ->object class

(define ->history:display ->object:display)
;inherits method from ->object class

(define (->history:set . internals)
  (let ((state (-state- internals))
        (arguments (-arguments- internals)))
    (apply ->history (:state (apply (apply ->object state) (list ':set arguments))))))
;need macros for generating method defs that produce new objects
;remember, these are stateless objects

(define ->history:datum ->object:datum)
;inherits method from ->object class
;another good reason for using macros

(define (->history:event . internals)
  (let ((state (-state- internals))
        (arguments (-arguments- internals)))
    (let* ((new (apply ->history state))
           (new (apply :set (list new 'H= (append arguments (:datum new 'H=))))))
      new)))
;add more methods if you like
;then just add the boiler plate

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (:state obj . args)
  (obj ':state args))

(define (:display obj . args)
  (obj ':display args))

(define (:set obj . args)
  (obj ':set args))

(define (:datum obj . args)
  (obj ':datum args))

(define (:event obj . args)
  (obj ':event args))
;this is the boiler plate which must be added for all new methods
;not very hard to add new classes if you want

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(let* ((h1 (->history))
       (h2 (->history 'H= 29))
       (h1 (:set h1 'H= 12))
       (h1 (:set h1 'H= 12 'Z= 30))
       (h2 (:event h2 13))
       (h1 (:event h1 27))
       (h1 (:event h1 5))
       (h2 (:event h2 8)))
  (list (:display h1) (:display h2)))
;all procedures can serialize effects in this way
;amazingly we have not used set! even once

;output:
;
;((->history H= (5 27 . 12) Z= 30) (->history H= (8 13 . 29)))

;Finally:
;
;my solution is not optimal because as was pointed out to me on this mailing list eval
does not play well with nesting
;I propose a solution would be to use a macro that adds explicit case statements
containing the the methods to be called
;as you can see the process I followed to derive a new class was quite mechanical in
nature so why don't we mechanize it?
;I would also like feedback on what I can do to improve efficiency (I will learn what I
need to make this work
;
;Feedback on usefulness for automated corectness proofs would be nice too.

Best,

-Zelah

____________________________________________________________
FREE 3D EARTH SCREENSAVER - Watch the Earth right on your desktop!
Check it out at http://www.inbox.com/earth


Posted on the users mailing list.