Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you show me how to rewrite functions in lisp?

Consider this javascript:

function addX(n)
{
  return 3 + n;
}
alert(addX(6)); //alerts 9
eval('var newFunc = ' + addX.toString().replace("3", "5") + ';');
alert(newFunc(10)); //alert 15

Please ignore the fact that it's of dubious use and methodology, dangerous, difficult to follow in a large codebase, and so on. It lets you modify the function, on the fly, based on input from the user. I don't have that demonstrated, but I just as easily could have.

I'm hoping you can show me how to do this in lisp. I've read a lot of tutorials, read a lot about macros, asked a broader question, tried a lot of things but ultimately have come up short.

I want to know how, in lisp, I can modify this function at runtime to instead add 5. Or whatever else the user might enter.

(define (addX n)
  (+ 3 n))

I am not looking for currying! I know I can do this:

(define addXCurry 
  (lambda (x)
    (lambda (n)
      (+ x n))))

(define add5 (addXCurry 5))
(add5 10)

But this is creating a function-factory.
I'm using a simple example because I want to completely understand the hard way on something simple enough.


Edit Thank you all for your answers. I guess my big hangup around macros (as I understand them), is that I haven't seen one that completely separates the modification from the writing. The javascript example is simple - but you can do more complex re-writings based on user input.

The macros I've seen are all based around "compile-time" (or programmer-written-time I suppose). Like in C++ you can't have a dynamic template parameter - it has to be known at compile time.

(It seems) In lisp you can't fundamentally alter a procedure at run-time the way you can in javascript because you lost the source. You can eval and redefine it, but you can't iterate through the elements of the list (the list being the function definition), inspect each element and decide whether or not to change it. The exception seems to be the examples in Rainer's answer, which are shaky ground.

like image 961
Tom Ritter Avatar asked Aug 02 '09 23:08

Tom Ritter


3 Answers

The difficult part is that Common Lisp (and some other Lisps) gets rid of the source code. Especially when a compiler is involved. By default the source code is gone and all is left is the machine code then. How to recover the Lisp source and in what shape?

The reason behind this: why should a CL program be required to keep the source? It could be completely compiled to machine code or C code and have no compiler/EVAL at runtime. The program could be running without much of a development environment (no compiler, etc.). The Common Lisp environment also should not be required to be able to 'uncompile' code to some kind of reconstructed source code.

Also it is generally complicated.

(let ((n 0) (step 2)) (defun foo () (incf n step)))

What is the source of above and how would you be able to change STEP? The function is depending on lexical binding.

Another complication:

(defun foo (n) (+ n #.(random 1.0)))

How to recover that? Every time Lisp reads the source text a random number will be read.

Another complication:

(setf (symbol-function 'foo) (compute-function))

You can set the function value with some arbitrarily computed function or a predefined function (like SIN). How to recover those, if they are compiled to machine code, loaded as machine code etc.?

If a Common Lisp implementation keeps source code, the FUNCTION-LAMBDA-EXPRESSION retrieves it.

There are two ways around this:

a) Tell Lisp the source code or to remember the source.

Provide the source.

(let* ((fs (copy-list '(lambda (n) (+ n 3))))
   (fc (compile nil fs)))
   (print (funcall fc 6))
   (setf (third (third fs)) 5)
   (setf fc (compile nil fs))
   (funcall fc 6))

Expanded example:

Write a macro DEFINE that both remembers the source and defines the function.

(defmacro define (&rest source)
  `(progn (setf (get ',(first source) :source) (list* 'defun ',source))
     (defun ,@source)))

Above puts the source code on the symbol property list under :SOURCE.

Now we can write a function that modifies the source and compiles it:

(defun modify (fname modifier)
  (let ((source (get fname :source)))
    (when source
      (setf (get fname :source) (funcall modifier source))
      (eval (get fname :source))
      (compile fname))))

Example definition:

(define addx (n) (+ n 3))

Example rewrite:

(modify 'addx (lambda (source) (setf (third (fourth source)) 6) source))

b) Some Common Lisp implementations implement a function called FUNCTION-LAMBDA-EXPRESSION (defined in ANSI Common Lisp).

This function returns three values: the source as Lisp data, closure-p and the name. It would allow you to change the source, compile it and set the name to the new function using COMPILE. Code example left as exercise.

Problem: in Common Lisp the macro DEFUN defines functions. What the macro does behind the scenes (book keeping for the IDE, code rewriting, ...) is implementation dependent. So the code that is returned by FUNCTION-LAMBDA-EXPRESSION (if the implementation returns source code) may look different for each implementation.

This is a LispWorks example:

CL-USER 12 > (function-lambda-expression #'addx)

(LAMBDA (N)
  (DECLARE (SYSTEM::SOURCE-LEVEL #<EQ Hash Table{0} 217874D3>))
  (DECLARE (LAMBDA-NAME ADDX))
  (+ N 3))
NIL
ADDX

So you could manipulate the source expression and change it.

like image 92
Rainer Joswig Avatar answered Sep 16 '22 14:09

Rainer Joswig


The exception seems to be the examples in Rainer's answer, which are shaky ground.

Why? It's the logical conclusion. You can't rely on the compiler preserving the source, so you just store it yourself.

After that you can properly work with the definition of the function (as opposed to Javascript where you just hack a string representation, which is a prime example of a shaky thing).

like image 41
Leslie P. Polzer Avatar answered Sep 16 '22 14:09

Leslie P. Polzer


Our procedure code:

(define add-x-code
  '(lambda (n)
     (+ 3 n)))

Evaluate it to a function:

(define add-x (eval add-x-code))
> (add-x 5)
8

Change it:

(define add-x-2 (eval (replace 3 7 add-x-code)))

> (add-x-2 5)
12

That seems similar to what you have done in the JavaScript code. Whether it is a good idea is not what you asked, so I'll leave that out.

(Simple replacement procedure I whipped up:)

(define (replace x y list)
  (map (lambda (x.)
         (if (equal? x. x)
             y
             (if (list? x.)
                 (replace x y x.)
                 x.)))
       list))
like image 33
Christopher Done Avatar answered Sep 20 '22 14:09

Christopher Done