Modifying function; saving to new function in lisp

2019-07-06 19:49发布

问题:

So I thought one of the advantages of lisp (among other languages) is its ability to implement function factories (accept functions as arguments; return new functions). I want to use this capability to make small changes to a function and save it as a new function so that if changes are made to the original function, they are also reflected in the new function on which it is based. Note: I am not the one writing the original function so I can't necessarily encapsulate the common parts in a separate function to be called by both, which would be the obvious answer otherwise.

Toy example in emacs lisp (may not be the most ideal as it is a lisp-2):

I have a function, foo that is provided to me:

(defun foo (x y)
    (+ x y)))

I want my new function to include a statement that allows me to change the value of a variable if a certain condition is met. For instance:

(defun newfoo (x y)
  (if (condition-met-p x) 
      (setq x (transform x)))
    (+ x y))

Please disregard that I could use defadvice in this particular example as I am more interested in the general task of modifying functions where defadvice may not apply. I believe I can modify the body with this form:

(setq conditional-transformation 
      '(if (condition-met x) (setq x (transform x))))

(setq newbody (append conditional-transformation 
              (nth 2 (symbol-function 'foo)))))

My questions are specifically how to

  1. create a copy of foo to newfoo and replace the body with the value of newbody defined above. (I've looked into fset, setf, and function but perhaps not using them properly.)
  2. possibly wrap this in a function called makenewfoo() or something like this so I can invoke makenewfoo(foo) and allow this to create newfoo().

And, more generally,

  1. is something like this is commonly done or there is a more idiomatic way to modify functions?
  2. this is a very simple case, but is there a more general way than specifying the list element number to nth for the modification. For instance, the actual function is more complex so is there a way to recursively search down this s-expression tree and test for a particular syntax and insert this conditional-transformation expression before or after it (possibly using equal), so it is less sensitive to changes made in the original function?

回答1:

It does work in Emacs Lisp:

elisp> (defun foo (x y)
         (+ x y))
foo
elisp> (fset 'newfoo
             (append (lambda (x y)
                       (when (< x 2)
                         (setq x (* x 2))))
                     (cddr (symbol-function 'foo))))
(lambda
  (x y)
  (when
      (< x 2)
    (setq x
          (* x 2)))
  (+ x y))

elisp> (newfoo 1 3)
5
elisp> (newfoo 3 3)
6

But I really don't think that it is commonly done or idiomatic. You should use defadvice if you want to modify the behavior of functions.

As far as CL is concerned: Some implementations provide similar functions/macros (for example in CCL: ccl:advise), and you can specify :before, :after, and :around methods for generic functions.


Example code for insertion of expressions:

(defun find-node (elt tree)
  (cond ((null tree) nil)
        ((equal (car tree) elt) tree)
        ((consp (car tree)) (let ((node (find-node elt (car tree))))
                              (if node node (find-node elt (cdr tree)))))
        (t (find-node elt (cdr tree)))))

(defun insert-before (node elt)
  (setcdr node (cons (car node) (cdr node)))
  (setcar node elt))

(let* ((function (copy-tree (symbol-function 'foo)))
       (node (find-node '(+ x y) function)))
  (when node
    (insert-before node '(if (< x 2) (setq x (* x 2))))
    (fset 'newfoo function)))