[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Prettier generic function arglists

A while back someone posted a suggestion for a way to get more readable
arglists from generic functions.  I thought that was a great idea, so, 
I wrote some code that does this. 

Say you do this:

(defmethod foo ((self class1) &rest rest-list &key a b &allow-other-keys)
  ... )

(defmethod foo ((self class2) &rest rest-list &key c (d 5) &allow-other-keys)
  ... )

(defmethod foo ((self class3) &rest other-stuff &key &allow-other-keys)
  ... )

Then, with the code that follows, the arglist of the generic function
instead of (#:|Disc-Fn-Arg-0| &REST #:|Disc-Fn-&Rest-Arg|)

Making the world a safer place to hack,

--- like, cut here ---
;;; -*- Mode:Lisp; Syntax:Common-Lisp; Package:PCL -*-

;;; Prettier arglists in PCL

(in-package "PCL")

(defun parse-method-pretty-arglist (meth)
  "Given a STANDARD-METHOD, returns five values:
     a list of the required arguments;
     a list of the optional arguments;
     a symbol, the rest arg, or NIL;
     a list of the keyword arguments;
     T or NIL, whether &ALLOW-OTHER-KEYS was present.
   All elements of the returned lists will be symbols."
  (let* ((required ())
	 (optional ())
	 (rest nil)
	 (keywords ())
	 (allow-other-p nil)
	 (arglist (method-arglist meth))
	 (current-type :required))
    (dolist (token arglist)
      (case token
	(&optional (setq current-type :optional))
	(&rest (setq current-type :rest))
	(&key (setq current-type :keyword))
	(&allow-other-keys (setq allow-other-p t))
	(&aux (setq current-type :aux))
	(t (unless (member token LAMBDA-LIST-KEYWORDS :test #'eq)
	     ;; If we get a non-CommonLisp lambda-list keyword, we just 
	     ;; pretend it wasn't there:  (a &special b) ==> (a b)
	     (ecase current-type
	       (:required (push token required))
	       (:optional (push token optional))
	       (:rest (setq rest token))
	       (:keyword (push token keywords))
	       (:aux nil))))))
    (labels ((safe-car (x) (if (consp x) (safe-car (car x)) x)))
      ;; SAFE-CAR is to handle FOO and (FOO 5), as well as &KEY ((:FOO *FOO*) 5)
      (setq required (mapcar #'safe-car required))
      (setq optional (mapcar #'safe-car optional))
      (setq keywords (mapcar #'safe-car keywords)))
    (values (nreverse required)
	    (nreverse optional)
	    (nreverse keywords)

(defun generic-function-comprehensive-arglist (gf)
  "Returns a lambda list describing the generic function GF.
  This list is derived by looking at the arglists of all the methods."
  (let* ((required ())
	 (optional ())
	 (rest nil)
	 (keywords ())
	 (allow-other-keys-p nil))
    (dolist (meth (generic-function-methods gf))
      (multiple-value-bind (req opt rst key otherp)
	                   (parse-method-pretty-arglist meth)
	;; Since all methods must have parallel arglists, the following
	;; form is a little redundant.  KEYWORDS is really the only variable
	;; that must be set for each method.  But we set the others so we
        ;; (lazilly) don't have to special case the first or last iteration
        ;; through the loop.
	(setq required req
	      optional opt
	      rest (or rest rst)
	      keywords (nconc key keywords)
	      allow-other-keys-p (or allow-other-keys-p otherp))))
    (setq keywords (delete-duplicates keywords))
    (let* ((result required))
      (when optional (setq result (nconc result (cons '&optional optional))))
      (when rest (setq result (nconc result (list '&rest rest))))
      (when keywords (setq result (nconc result (cons '&key keywords))))
      (when allow-other-keys-p (setq result (nconc result (list '&allow-other-keys))))

;;; The next bit will install this code into the ARGLIST function in
;;; Lucid Lisp.  It would probably be cooler if this code was instead
;;; hooked in to the place in PCL where the generic function's arglist is
;;; calculated, but I don't want to deal with figuring out where that is...
(sys::defadvice (sys::arglist generic-function-arglist) (function)
  (when (symbolp function) (setq function (symbol-function function)))
  (if (generic-function-p function)
      (generic-function-comprehensive-arglist function)
      (sys::advice-continue function)))