Building a generic Lisp function programmatically with defun

I would like to define a function using defun

, but not at the top level. (The function name and body must be constructed from some user input.) The main purpose is illustrated below:

(let ((name 'fn1)
      (body "abc"))
  (eval `(defun ,name ()
           ,body))
  (push name *function-names*))

      

1) It works, but how can it be done without eval

?

2) I am currently compiling a number of such functions with

(loop for fn-name in *function-names* do
      (setf (symbol-function fn-name)
        (compile nil (symbol-function fn-name))))

      

but is there a better way to use just the fn name like `(compile, fn-name), which avoids the SBCL compiler warning when the function body contains a recursive function call?

+3


source to share


2 answers


Doing it without DEFUN

makes it a little problematic. I think the ANSI Common Lisp part that is not that great.

If you look at Common Lisp implementations, they expand the form DEFUN

in implementation-specific constructs. Often we will see something like a named lambda.

SBCL:

(defun foo ()
  (foo))

      

->

(SB-INT:NAMED-LAMBDA FOO ()
  (BLOCK FOO (FOO)))

      

which evaluates to:

#<FUNCTION FOO {1003271FFB}>

      

Unfortunately, the named lambda is not a concept in ANSI CL.

To do this, you need:

  • build a DEFUN form
  • EVALuate it
  • optional COMPILE it


To get a similar effect, we could create a file, compile and load it.

Alternatively, we could try not to use DEFUN.

  • we need to make sure the global name is not a macro
  • then we build a lambda expression
  • then we COMPILE this expression

Example: recursive function:

(compile 'my-fn '(lambda () (my-fn))

      

Now we can get a warning about undefined function when compiling the lambda. How can we provide a name? LABELS will do it:

(compile 'my-fn
         (lambda ()
           (labels ((my-fn ()
                      (my-fn)))
             (my-fn))))

      

LABELS will also set BLOCK, like DEFUN.

When we now call the global MY-FN, it then calls the local MY-FN, which can then call itself recursively.

Let's see if there are other alternative approaches ...

  • You can also use a regular LAMBDA with COMPILE and declare the function first. It can suppress the compile warning as well.
+3


source


I'm a little confused about “you are trying to do this with defun

, but if you want the user to be able to define their own functions and then call them, there are several ways you can do it.

1) if you evaluate it as a normal function defun

then you can just do something like

(loop for f in *my-functions-names* and
      for a in *my-functions-args* and
      for b in *my-functions-bodies*
  do (setf (symbol-function f) (compile `(lambda ,a ,b))))

      

then users could compile their functions at runtime and it would look like it was there all along.

2) create a hash table of lambdas and funcall

/ or apply

your functions:

(defparameter *user-functions* (make-hash-table))

(defun def-user-fun (f-name f-args f-body &optional (ns *user-functions*))
  (setf (gethash f-name ns) (compile nil `(lambda ,f-args ,f-body))))

(defun call-user-fun (f-name args &optional (ns *user-functions*))
  (funcall (gethash f-name *user-funcations) f-args))

      

This will allow users to define what features they want to be evaluated in the current environment.

3) if it's just to save time writing your own code, then you can just do what you did, but with defmacro and a loop operator.

Update

Since you need recursion, you can do something similar to what @rainerjoswig suggested with (lambda (args..) (labels ((recurse ...)) (recurse ...)))

. While it may not sound "pretty" in reality, it is idiomatic in Lisp. In fact, the SBCL compiler and garbage collector are specifically tuned for this kind of recursion. If you want to learn about common Lisp optimizations that you can rely on, you should read the standard .



Is it (compile 'fname) == (setf (symbol-function 'fname) (compile nil (symbol-function 'fname)))

? Yes and no. At least, perhaps not in the way you think. It seems to me that you are confused about two things, both of which are part of the secret mechanism of the Lisp backend.

First, symbol-function

it is not an evaluator, it just lets the Lisp reader know that this symbol is a function symbol and should be accessible through the functional environment, not the current lexical environment, that is, the function is accessed as a variable, but in a different environment or namespace ... I would say that against the backdrop of what labels

gets enhanced this feature.

Second, it compile

also doesn't work. You can pass it a name symbol-function

, which it accesses the function definition saved , compiles it, and then the old definition with the compiled definition (compile 'my-func)

, you can pass it an lambda

expression that it will compile, (compile nil (lambda (args...) body))

or finally, you can pass both the name and the definition. which will store the compiled function as this function variable.

So, yes, in a sense it expands to setf

, but doesn't match your specific one setf

, because you shouldn't (compile nil (symbol-function 'fname))

.

As far as resolving recursion is concerned, simply expand on @rainerjoswig by destructuring the generic form defun

like

(def-user-func count-down (val) 
  (if (> val 0)
    (progn (print val) (count-down (1- val)))
    nil))

      

with a macro like

(defmacro def-user-func (name args &body body)
  `(compile ',name (lambda (,@args)
                     (labels ((,name (,@args)
                                ,@body))
                        (,name ,@args)))))

      

Because Lisp dims variable names inside let

, labels

and other operators like it, this will work just fine. The "outside" world just sees the function, and the function just sees its own function labels

. When ,@body

expanded, it will do so in context labels

, and since we are duplicating names, it will work correctly.

+2


source







All Articles