summaryrefslogtreecommitdiff
path: root/modules/oop/pf-objects.scm
diff options
context:
space:
mode:
Diffstat (limited to 'modules/oop/pf-objects.scm')
-rw-r--r--modules/oop/pf-objects.scm528
1 files changed, 528 insertions, 0 deletions
diff --git a/modules/oop/pf-objects.scm b/modules/oop/pf-objects.scm
new file mode 100644
index 0000000..4ff3d23
--- /dev/null
+++ b/modules/oop/pf-objects.scm
@@ -0,0 +1,528 @@
+(define-module (oop pf-objects)
+ #:use-module (oop goops)
+ #:use-module (ice-9 vlist)
+ #:export (set ref make-pf <pf> call with copy fset fcall make-p put put!
+ pcall pcall! get
+ mk
+ def-pf-class mk-pf-class make-pf-class
+ def-p-class mk-p-class make-p-class
+ def-pyf-class mk-pyf-class make-pyf-class
+ def-py-class mk-py-class make-py-class))
+
+#|
+Python object system is basically syntactic suger otop of a hashmap and one
+this project is inspired by the python object system and what it measn when
+one in stead of hasmaps use functional hashmaps. We use vhashes, but those have a drawback in that those are not thread safe. But it is a small effort to work
+with assocs or tree like functional hashmaps in stead.
+
+The hashmap works like an assoc e.g. we will define new values by 'consing' a
+new binding on the list and when the assoc take up too much space it will be
+reshaped and all extra bindings will be removed.
+
+The datastructure is functional but the objects mutate. So one need to
+explicitly tell it to not update etc.
+|#
+
+(define-syntax-rule (aif it p x y) (let ((it p)) (if it x y)))
+
+(define-class <p> () h)
+(define-class <pf> (<p>) size n) ; the pf object consist of a functional
+ ; hashmap it's size and number of live
+ ; object
+(define-class <py> (<p>))
+(define-class <pyf> (<pf>))
+
+;; Make an empty pf object
+(define (make-pf)
+ (define r (make <pf>))
+ (slot-set! r 'h vlist-null)
+ (slot-set! r 'size 0)
+ (slot-set! r 'n 0)
+ r)
+
+(define (make-p)
+ (define r (make <p>))
+ (slot-set! r 'h (make-hash-table))
+ r)
+
+(define fail (cons 'fail '()))
+(define-syntax-rule (mrefx x key l)
+ (let ((h (slot-ref x 'h)))
+ (define pair (vhash-assq key h))
+ (define (end)
+ (if (null? l)
+ #f
+ (car l)))
+ (define (parents)
+ (let ((pair (vhash-assq '__parents__ h)))
+ (if (pair? pair)
+ (let lp ((li (cdr pair)))
+ (if (pair? li)
+ (let ((r (ref (car li) key fail)))
+ (if (eq? r fail)
+ (lp (cdr li))
+ r))
+ (end)))
+ (end))))
+
+ (if pair
+ (cdr pair)
+ (let ((cl (ref x '__class__)))
+ (if cl
+ (let ((r (ref cl key fail)))
+ (if (eq? r fail)
+ (parents)
+ r))
+ (parents))))))
+
+(define-syntax-rule (mrefx- x key l)
+ (let* ((h (slot-ref x 'h))
+ (r (hash-ref x key fail)))
+ (if (eq? r fail)
+ (if (pair? l)
+ (car l)
+ #f)
+ r)))
+
+(define not-implemented (cons 'not 'implemeneted))
+
+(define-syntax-rule (mrefx-py- x key l)
+ (let ((f (mref- x '__ref__ '())))
+ (if (or (not f) (eq? f not-implemented))
+ (mref- x key l)
+ (apply f x key l))))
+
+(define-syntax-rule (mrefx-py x key l)
+ (let ((f (mref x '__ref__ '())))
+ (if (or (not f) (eq? f not-implemented))
+ (mref x key l)
+ (apply f x key l))))
+
+(define-syntax-rule (unx mrefx- mref-)
+ (define-syntax-rule (mref- x key l)
+ (let ((xx x))
+ (let ((res (mrefx- xx key l)))
+ (if (procedure? res)
+ (lambda z
+ (apply res xx z))
+ res)))))
+
+(unx mrefx- mref-)
+(unx mrefx mref)
+(unx mrefx-py mref-py)
+(unx mrefx-py- mref-py-)
+
+(define-method (ref (x <pf> ) key . l) (mref x key l))
+(define-method (ref (x <p> ) key . l) (mref- x key l))
+(define-method (ref (x <pyf>) key . l) (mref-py x key l))
+(define-method (ref (x <py> ) key . l) (mref-py- x key l))
+
+
+
+;; the reshape function that will create a fresh new pf object with less size
+;; this is an expensive operation and will only be done when we now there is
+;; a lot to gain essentially tho complexity is as in the number of set
+(define (reshape x)
+ (let ((h (slot-ref x 'h))
+ (m (make-hash-table))
+ (n 0))
+ (define h2 (vhash-fold (lambda (k v s)
+ (if (hash-ref m k #f)
+ s
+ (begin
+ (hash-set! m k #t)
+ (set! n (+ n 1))
+ (vhash-consq k v s))))
+ vlist-null
+ h))
+ (slot-set! x 'h h2)
+ (slot-set! x 'size n)
+ (slot-set! x 'n n)
+ (values)))
+
+;; on object x add a binding that key -> val
+(define-syntax-rule (mset x key val)
+ (let ((h (slot-ref x 'h))
+ (s (slot-ref x 'size))
+ (n (slot-ref x 'n)))
+ (slot-set! x 'size (+ 1 s))
+ (let ((r (vhash-assq key h)))
+ (when (not r)
+ (slot-set! x 'n (+ n 1)))
+ (slot-set! x 'h (vhash-consq key val h))
+ (when (> s (* 2 n))
+ (reshape x))
+ (values))))
+
+(define-syntax-rule (mset-py x key val)
+ (let ((f (mref-py x '__set__ '())))
+ (if (or (eq? f not-implemented) (not f))
+ (mset x key val)
+ (f key val))))
+
+
+(define-syntax-rule (mset- x key val)
+ (let ((h (slot-ref x 'h)))
+ (hash-set! h key val)))
+
+(define-syntax-rule (mset-py- x key val)
+ (let ((f (mref-py- x '__set__ '())))
+ (if (or (eq? f not-implemented) (not f))
+ (mset- x key val)
+ (f key val))))
+
+(define-method (set (x <pf>) key val) (mset x key val))
+(define-method (set (x <p>) key val) (mset- x key val))
+(define-method (set (x <pyf>) key val) (mset-py x key val))
+(define-method (set (x <py>) key val) (mset-py- x key val))
+
+
+;; mref will reference the value of the key in the object x, an extra default
+;; parameter will tell what the fail object is else #f if fail
+;; if there is no found binding in the object search the class and
+;; the super classes for a binding
+
+
+;; call a function as a value of key in x with the object otself as a first
+;; parameter, this is pythonic object semantics
+(define-syntax-rule (mk-call mcall mref)
+ (define-syntax-rule (mcall x key l)
+ (apply (mref x key '()) l)))
+
+(mk-call mcall mref)
+(mk-call mcall- mref-)
+(mk-call mcall-py mref-py)
+(mk-call mcall-py- mref-py-)
+
+(define-method (call (x <pf>) key . l) (mcall x key l))
+(define-method (call (x <p>) key . l) (mcall- x key l))
+(define-method (call (x <pyf>) key . l) (mcall-py x key l))
+(define-method (call (x <py>) key . l) (mcall-py- x key l))
+
+
+;; make a copy of a pf object
+(define-syntax-rule (mcopy x)
+ (let ((r (make <pf>)))
+ (slot-set! r 'h (slot-ref x 'h))
+ (slot-set! r 'size (slot-ref x 'size))
+ (slot-set! r 'n (slot-ref x 'n))
+ r))
+
+(define-syntax-rule (mcopy- x)
+ (let* ((r (make-p))
+ (h (slot-ref r 'h)))
+ (hash-for-each (lambda (k v) (hash-set! h k v)) (slot-ref x 'h))
+ r))
+
+(define-method (copy (x <pf>)) (mcopy x))
+(define-method (copy (x <p> )) (mcopy- x))
+
+
+;; with will execute thunk and restor x to it's initial state after it has
+;; finished note that this is a cheap operatoin because we use a functional
+;; datastructure
+(define-syntax-rule (mwith x thunk)
+ (let ((old (mcopy x)))
+ (let ((r (thunk)))
+ (slot-set! x 'h (slot-ref old 'h))
+ (slot-set! x 'size (slot-ref old 'size))
+ (slot-set! x 'n (slot-ref old 'n))
+ r)))
+
+(define-syntax-rule (mwith- x thunk)
+ (let ((old (mcopy- x)))
+ (let ((r (thunk)))
+ (slot-set! x 'h (slot-ref old 'h))
+ r)))
+
+
+
+;; a functional set will return a new object with the added binding and keep
+;; x untouched
+(define-method (fset (x <pf>) key val)
+ (let ((x (mcopy x)))
+ (mset x key val)
+ x))
+
+(define-method (fset (x <p>) key val)
+ (let ((x (mcopy- x)))
+ (mset x key val)
+ x))
+
+;; a functional call will keep x untouched and return (values fknval newx)
+;; e.g. we get both the value of the call and the new version of x with
+;; perhaps new bindings added
+(define-method (fcall (x <pf>) key . l)
+ (let* ((y (mcopy x))
+ (r (mcall y key l)))
+ (if (eq? (slot-ref x 'h) (slot-ref y 'h))
+ (values r x)
+ (values r y))))
+
+(define-method (fcall (x <p>) key . l)
+ (let ((x (mcopy x)))
+ (values (mcall- x key l)
+ x)))
+
+;; this shows how we can override addition in a pythonic way
+(define-syntax-rule (mk-arith + +x __add__ __radd__)
+ (begin
+ (define-method (+ (x <p>) y)
+ (call x '__add__ y))
+
+ (define-method (+ x (y <p>))
+ (call y '__radd__ x))
+
+ (define-method (+ (x <py>) y)
+ (let ((f (mref-py- x '__add__ '())))
+ (if f
+ (f y)
+ (+x y x))))
+
+ (define-method (+ (x <pyf>) y)
+ (let ((f (mref-py x '__add__ '())))
+ (if f
+ (let ((res (f y)))
+ (if (eq? res not-implemented)
+ (+x y x)
+ res))
+ (+x y x))))
+
+ (define-method (+ (x <py>) y)
+ (let ((f (mref-py- x '__add__ '())))
+ (if f
+ (let ((res (f y)))
+ (if (eq? res not-implemented)
+ (+x y x)
+ res))
+ (+x y x))))
+
+ (define-method (+ x (y <py>))
+ (call y '__radd__ x))
+
+ (define-method (+ x (y <pyf>))
+ (call y '__radd__ x))
+
+ (define-method (+x (x <p>) y)
+ (call x '__radd__ y))))
+
+;; A few arithmetic operations at service
+(mk-arith + +x __add__ __radd__)
+(mk-arith - -x __sub__ __rsub__)
+(mk-arith * *x __mul__ __rmul__)
+
+;; lets define get put pcall etc so that we can refer to an object like
+;; e.g. (put x.y.z 1) (pcall x.y 1)
+
+(define-syntax-rule (cross x k f set)
+ (call-with-values (lambda () f)
+ (lambda (r y)
+ (if (eq? x y)
+ (values r x)
+ (values r (set x k y))))))
+
+(define-syntax-rule (cross! x k f _) f)
+
+(define-syntax mku
+ (syntax-rules ()
+ ((_ cross set setx f (key) (val ...))
+ (setx f key val ...))
+ ((_ cross set setx f (k . l) val)
+ (cross f k (mku cross set setx (ref f k) l val) set))))
+
+(define-syntax-rule (mkk pset setx set cross)
+ (define-syntax pset
+ (lambda (x)
+ (syntax-case x ()
+ ((_ f val (... ...))
+ (let* ((to (lambda (x)
+ (datum->syntax #'f (string->symbol x))))
+ (l (string-split (symbol->string (syntax->datum #'f)) #\.)))
+ (with-syntax (((a (... ...)) (map (lambda (x) #`'#,(to x))
+ (cdr l)))
+ (h (to (car l))))
+ #'(mku cross setx set h (a (... ...)) (val (... ...))))))))))
+
+(mkk put fset fset cross)
+(mkk put! set set cross!)
+(mkk pcall! call fset cross!)
+(mkk pcall fcall fset cross)
+(mkk get ref fset cross!)
+
+;; it's good to have a null object so we don't need to construct it all the
+;; time because it is functional we can get away with this.
+(define null (make-pf))
+
+;; append the bindings in x in front of y + some optimizations
+(define (union x y)
+ (define hx (slot-ref x 'h))
+ (define hy (slot-ref y 'h))
+ (define n (slot-ref x 'n))
+ (define s (slot-ref x 'size))
+ (define m (make-hash-table))
+
+ (define h
+ (vhash-fold
+ (lambda (k v st)
+ (if (vhash-assq k hy)
+ (begin
+ (set! s (+ s 1))
+ (vhash-consq k v st))
+ (if (hash-ref m k)
+ s
+ (begin
+ (set! n (+ n 1))
+ (set! s (+ s 1))
+ (hash-set! m k #t)
+ (vhash-consq k v st)))))
+ hy
+ hx))
+
+ (define out (make <pf>))
+ (slot-set! out 'h h)
+ (slot-set! out 'n n)
+ (slot-set! out 'size s)
+ out)
+
+(define (union- x y)
+ (define hx (slot-ref x 'h))
+ (define hy (slot-ref y 'h))
+ (define out (make <p>))
+ (hash-for-each (lambda (k v) (hash-set! hy k v)) hx)
+ (slot-set! out 'h hy)
+ out)
+
+
+;; make a class. A class add some meta information to allow for multiple
+;; inherritance and add effectively static data to the object the functional
+;; datastructure show it's effeciency now const is data that will not change
+;; and bindings that are added to all objects. Dynamic is the mutating class
+;; information. supers is a list of priorities
+(define-syntax-rule (mk-pf make-pf-class <pf>)
+ (define-syntax make-pf-class
+ (lambda (x)
+ (syntax-case x ()
+ ((_ name const dynamic (supers (... ...)))
+ (with-syntax (((sups (... ...)) (generate-temporaries
+ #'(supers (... ...)))))
+ #'(let ((sups supers) (... ...))
+ (define class dynamic)
+ (define-class name (sups (... ...) <pf>))
+ (put! class.__const__
+ (union const
+ (let lp ((sup (list sups (... ...))))
+ (if (pair? sup)
+ (union (ref (car sup) '__const__ null)
+ (lp (cdr sup)))
+ null))))
+
+ (reshape (get class.__const__ null))
+
+ (put! class.__goops__ name)
+ (put! class.__name__ 'name)
+ (put! class.__parents__ (list sups (... ...)))
+
+ (put! class.__const__.__name__ (cons 'name 'obj))
+ (put! class.__const__.__class__ class)
+ (put! class.__const__.__parents__ (list sups (... ...)))
+ class)))))))
+
+(mk-pf make-pf-class <pf>)
+(mk-pf make-pyf-class <pyf>)
+
+(define-syntax-rule (mk-p make-p-class <p>)
+ (define-syntax make-p-class
+ (lambda (x)
+ (syntax-case x ()
+ ((_ name const dynamic (supers (... ...)))
+ (with-syntax (((sups (... ...)) (generate-temporaries
+ #'(supers (... ...)))))
+ #'(let ((sups supers) (... ...))
+ (define class dynamic)
+ (define-class name (sups (... ...) <p>))
+ (put! class.__const__
+ (union- const
+ (let lp ((sup (list sups (... ...))))
+ (if (pair? sup)
+ (union- (ref (car sup) '__const__ null)
+ (lp (cdr sup)))
+ (make-p)))))
+
+
+ (put! class.__goops__ name)
+ (put! class.__name__ 'name)
+ (put! class.__parents__ (list sups (... ...)))
+
+ (put! class.__const__.__name__ (cons 'name 'obj))
+ (put! class.__const__.__class__ class)
+ (put! class.__const__.__parents__ (list sups (... ...)))
+
+ (union- class (get class.__const__)))))))))
+
+(mk-p make-p-class <p>)
+(mk-p make-py-class <py>)
+
+;; Let's make an object essentially just move a reference
+(define-method (mk (x <pf>) . l)
+ (let ((r (get x.__const__))
+ (k (make (get x.__goops__))))
+ (slot-set! k 'h (slot-ref r 'h))
+ (slot-set! k 'size (slot-ref r 'size))
+ (slot-set! k 'n (slot-ref r 'n))
+ (apply (ref k '__init__ (lambda x (values))) k l)
+ k))
+
+(define-method (mk (x <p>) . l)
+ (let ((k (make (get x.__goops__))))
+ (put! k.__class__ x)
+ (apply (ref k '__init__ (lambda x (values))) k l)
+ k))
+
+;; the make class and defclass syntactic sugar
+(define-syntax-rule (mk-p/f make-pf mk-pf-class make-pf-class)
+ (define-syntax-rule (mk-pf-class name (parents (... ...))
+ #:const
+ ((sdef mname sval) (... ...))
+ #:dynamic
+ ((ddef dname dval) (... ...)))
+ (let ()
+ (define name
+ (make-pf-class name
+ (let ((s (make-pf)))
+ (set s 'mname sval) (... ...)
+ s)
+ (let ((d (make-pf)))
+ (set d 'dname dval) (... ...)
+ d)
+ (parents (... ...))))
+ name)))
+
+(mk-p/f make-pf mk-pf-class make-pf-class)
+(mk-p/f make-p mk-p-class make-p-class)
+(mk-p/f make-pf mk-pyf-class make-pyf-class)
+(mk-p/f make-p mk-py-class make-py-class)
+
+(define-syntax-rule (def-pf-class name . l)
+ (define name (mk-pf-class name . l)))
+
+(define-syntax-rule (def-p-class name . l)
+ (define name (mk-p-class name . l)))
+
+(define-syntax-rule (def-pyf-class name . l)
+ (define name (mk-pyf-class name . l)))
+
+(define-syntax-rule (def-py-class name . l)
+ (define name (mk-py-class name . l)))
+
+(define-syntax-rule (wrap class)
+ (let* ((c class)
+ (ret (lambda x (apply mk c x))))
+ (set-procedure-property! ret 'pyclass class)
+ ret))
+
+(define (get-class x)
+ (aif it (procedure-property x 'pyclass)
+ it
+ (error "not a class")))
+
+