(in-package "SPECIALIZABLE") (defclass prototype-object () (;; FIXME: we should add slots at some point (delegations :initarg :delegations :accessor delegations) (roles :initform (make-array 0 :adjustable t :fill-pointer t) :accessor roles) ;; debugging aid (name))) (defmethod print-object ((o prototype-object) s) (if (slot-boundp o 'name) (format s "~S" (slot-value o 'name)) (print-unreadable-object (o s :type t :identity t) (format s "[~{~S~^, ~}]" (delegations o))))) (defun add-delegation (obj del) (push del (delegations obj))) (defun remove-delegation (obj) (pop (delegations obj))) (defun map-delegations (fun obj) (funcall fun obj) ;; FIXME: should we maintain a table of visited nodes? Should it be ;; topologically sorted? Section 5.3 in PwMD [Salzman & Aldrich] ;; suggests not, at least for now (mapc (lambda (o) (map-delegations fun o)) (delegations obj)) nil) (defstruct (role (:type list) (:constructor make-role (method argpos))) method argpos) (defun add-role (obj role) (let ((pos (role-argpos role)) (roles (roles obj))) (unless (< pos (length roles)) (dotimes (i (- (1+ pos) (length roles))) (vector-push-extend nil roles))) (pushnew (role-method role) (aref roles pos)))) (defun remove-role (obj role) (let ((pos (role-argpos role))) (setf (aref (roles obj) pos) (remove (role-method role) (aref (roles obj) pos))) (tagbody start (when (or (= (length (roles obj)) 0) (aref (roles obj) (1- (length (roles obj))))) (go done)) (vector-pop (roles obj)) (go start) done))) (defun map-roles (fun obj) (dotimes (i (length (roles obj))) (dolist (m (aref (roles obj) i)) (funcall fun (make-role m i))))) (defun find-role (role obj) (when (< (role-argpos role) (length (roles obj))) (find (role-method role) (aref (roles obj) (role-argpos role))))) (defmacro do-roles ((rvar form &optional result) &body body) `(progn (map-roles (lambda (,rvar) ,@body) ,form) ,result)) (defun clone (p) (let ((result (make-instance 'prototype-object :delegations (copy-list (delegations p))))) (do-roles (r p result) (add-role result r)))) ;;; redefinition semantics are interesting. We need the INFO here so ;;; that we can implement specializer-accepts-p, which must be able to ;;; lookup the particular method/argpos that the specializer ;;; represents. But we must also be able to redefine methods in a way ;;; that isn't insane, which I think implies that same-specializer-p ;;; should ignore the INFO and just use the OBJECT. (defclass prototype-specializer (extended-specializer) ((role :accessor prototype-specializer-role) (object :initarg :object :accessor prototype-specializer-object))) (defmethod print-object ((o prototype-specializer) s) (print-unreadable-object (o s :type t :identity t) (format s "~S" (prototype-specializer-object o)))) (defmethod sb-pcl::same-specializer-p ((s1 prototype-specializer) (s2 prototype-specializer)) (eql (prototype-specializer-object s1) (prototype-specializer-object s2))) (defclass prototype-generic-function (specializable-generic-function) () (:metaclass sb-mop:funcallable-standard-class)) (defmethod sb-pcl:make-method-specializers-form ((gf prototype-generic-function) method snames env) (flet ((frob (x) (typecase x (sb-mop:specializer x) (symbol `(make-instance 'prototype-specializer :object ,x)) ((cons (eql 'class)) `(find-class ',(cadr x))) ((cons (eql 'eql)) `(sb-mop:intern-eql-specializer ,(cadr x))) (t (error "unexpected specializer name: ~S" x))))) `(list ,@(mapcar #'frob snames)))) (defmethod sb-pcl:parse-specializer-using-class ((gf prototype-generic-function) name) (make-instance 'prototype-specializer :object name)) (defmethod sb-pcl:unparse-specializer-using-class ((gf prototype-generic-function) (s prototype-specializer)) (let ((object (prototype-specializer-object s))) (if (slot-boundp object 'name) (slot-value object 'name) s))) (defmethod add-method :after ((gf prototype-generic-function) m) (let ((ss (sb-mop:method-specializers m))) (do* ((i 0 (1+ i)) (ss ss (cdr ss)) (s (car ss) (car ss))) ((null ss)) (when (typep s 'prototype-specializer) (let ((object (prototype-specializer-object s)) (role (make-role m i))) (setf (prototype-specializer-role s) role) (add-role object role)))))) (defmethod remove-method :after ((gf prototype-generic-function) m) (let ((ss (sb-mop:method-specializers m))) (do* ((i 0 (1+ i)) (ss ss (cdr ss)) (s (car ss) (car ss))) ((null ss)) (when (typep s 'prototype-specializer) (let ((object (prototype-specializer-object s)) (role (make-role m i))) (setf (prototype-specializer-role s) nil) ;; this is one of the places where the semantics ;; are... dodgy. Removing the method from the generic ;; function, and the role from the object, doesn't affect ;; the roles in any clones. We could potentially use the ;; fact that once removed the method is no longer associated ;; with a generic function? Hm, C-A-M will not consider the ;; removed method for applicability... (remove-role object role)))))) (defmethod generalizer-of-using-class ((gf prototype-generic-function) (object prototype-object)) object) (defmethod specializer-accepts-generalizer-p ((gf prototype-generic-function) (s prototype-specializer) object) (values (specializer-accepts-p s object) t)) (defmethod specializer-accepts-p ((specializer prototype-specializer) object) (cond ((not (typep object 'prototype-object)) nil) ((eql (prototype-specializer-object specializer) /root/) t) (t (let ((role (prototype-specializer-role specializer))) (map-delegations (lambda (o) (when (find-role role o) (return-from specializer-accepts-p t))) object))))) (defmethod specializer< ((gf prototype-generic-function) (s1 prototype-specializer) (s2 prototype-specializer) g) (let ((o1 (prototype-specializer-object s1)) (o2 (prototype-specializer-object s2))) (map-delegations (lambda (o) (cond ((eql o o1) (return-from specializer< '<)) ((eql o o2) (return-from specializer< '>)))) g) '=)) (defmethod compute-applicable-methods-using-generalizers ((gf prototype-generic-function) generalizers) (values nil nil)) (defmethod generalizer-equal-hash-key ((gf prototype-generic-function) (g prototype-object)) g) (defmacro defpvar (name value) `(let ((val ,value)) (setf (slot-value val 'name) ',name) (defparameter ,name val))) (defpvar /root/ (make-instance 'prototype-object :delegations nil)) (defpvar /animal/ (clone /root/)) (defpvar /fish/ (clone /root/)) (defpvar /shark/ (clone /root/)) (defpvar /healthy-shark/ (clone /root/)) (defpvar /dying-shark/ (clone /root/)) (add-delegation /fish/ /animal/) (add-delegation /shark/ /animal/) (add-delegation /shark/ /healthy-shark/) (defgeneric encounter (x y) (:generic-function-class prototype-generic-function)) (defmethod encounter ((x /fish/) (y /healthy-shark/)) (format t "~&~A swims away~%" x)) (defmethod encounter ((x /fish/) (y /animal/)) x) (defgeneric fight (x y) (:generic-function-class prototype-generic-function)) (defmethod fight ((x /healthy-shark/) (y /shark/)) (remove-delegation x) (add-delegation x /dying-shark/) x) (defmethod encounter ((x /healthy-shark/) (y /fish/)) (format t "~&~A swallows ~A~%" x y)) (defmethod encounter ((x /healthy-shark/) (y /shark/)) (format t "~&~A fights ~A~%" x y) (fight x y))