How to stop evaluating lisp form when passed as fu

2019-05-27 02:20发布

问题:

I am learning Lisp. Now I am trying to create a function that takes some valid Lisp form as argument and returns a function that executes the Lisp forms when called. For example:

(defun fn (name action)
  (setf (symbol-function name)
        #'(lambda () action)))

When I am passing say (+ 4 5 6) the function is getting created with specific name and when called returning the sum.

(fn 'add (+ 4 5 6))
(add) ==> 15

But if I invoke (fn 'error (assert (= 2 3)) it is throwing error (= 2 3) must evaluate to a non-NIL value. and the function with name error is not created.

How can I stop this evaluation of assert when passed as function parameter?

回答1:

You cannot write this function; it has to be a macro operator. If fn is a function, then the call:

(fn 'add (+ 4 5 6))

evaluates the argument (+ 4 5 6), reducing it to the value 15. The function receives 15, and not the expression. We can "fix" this by quoting the code:

(fn 'add '(+ 4 5 6))

but then we have the problem that the code doesn't interact with the lexical environment. For instance, this won't work, because x is not visible inside fn:

(let ((x 40)) (fn 'add '(+ x 2)))

To create a function which evaluates (+ x 2) in the proper environment, we must the lambda operator right in that same lexical scope:

(let ((x 40)) (lambda () (+ x 2)))

Your fn operator can be written as a syntactic sugar that generates the lambda (without any name):

(defmacro fn (expr) `(lambda () ,expr))

Now we can write:

(let ((x 40)) (fn (+ x 2))) ;; returns a function which returns 42

To do the named thing:

(defmacro fn (name expr) `(setf (symbol-function ',name) (lambda () ,expr)))

However, this is a quite a poor idea; we're introducing a nasty global side effect into a function. A better "named fn" might be one which introduces a lexical binding for a function over some forms. That is, it can be used like this:

(fn (foo (+ x 2)) (foo))
             ;;  ^^^^^^  foo is a lexical function in this scope
             ;;          denoting the function (lambda () (+ x 2))

That could be done like this:

(defmacro fn ((name expr) &rest forms)
   `(flet ((,name () ,expr)) ,@forms)))

Or, if you want the name as a variable binding rather than a function binding, so that the usage is (fn (foo (+ x 2)) (funcall foo)):

(defmacro fn ((name expr) &rest forms)
  `(let ((,name (lambda () ,expr))) ,@forms))


回答2:

Create the function, compile it and store it under name:

(defun fn (name action)
  (compile name
           `(lambda () ,action)))

Let's try it:

CL-USER 13 > (fn 'add '(+ 4 5 6))
ADD
NIL
NIL

CL-USER 14 > (add)
15


回答3:

(defun fn (name action) (setf (symbol-function name) #'(lambda () action)))

(fn 'add (+ 4 5 6)) (add) ==> 15

This doesn't treat add and (+ 4 5 6) in the same way. You quote one (because you want a symbol), but not the other, even though you want a list. To get the behavior you want, you'll either need to define a macro so that you can prevent evaluation and put the form inside a function, or to construct a list that you coerce to a function. The macro approach:

(defmacro make-fn (name form)
  `(setf (symbol-function ',name) #'(lambda () ,form)))
CL-USER> (make-fn add (+ 4 5 6))
#<FUNCTION (LAMBDA ()) {1002D48D09}>
CL-USER> (add)
15
CL-USER> (make-fn err (assert (= 2 3)))
#<FUNCTION (LAMBDA ()) {1002E11359}>
CL-USER> (err)
; Evaluation aborted on #<SIMPLE-ERROR "The assertion ~S failed." {1002E24951}>.

The function and coerce approach:

(defun make-fn2 (name form)
  (setf (symbol-function name) (coerce (list 'lambda () form) 'function)))
CL-USER> (make-fn2 'add '(+ 4 5 6))
#<FUNCTION (LAMBDA ()) {1004566CB9}>
CL-USER> (add)
15
CL-USER> (make-fn2 'err '(assert (= 2 3)))
#<FUNCTION (LAMBDA ()) {100298D2F9}>
CL-USER> (err)
; Evaluation aborted on #<SIMPLE-ERROR "The assertion ~S failed." {10029CF441}>.

Now, these approaches will work fine, but Rainer Joswig's answer points out that there's a standard function that already does most of this work for us: compile. It's pretty versatile, but the important parts are that it takes a name and optionally a function definition. The function definition can be a lambda expression, and it will be coerced to a function (as above), but also compiled (since the simple coercion above might not compile the function) and store it as the function definition of the name, if name is non-nil. That means that compile will do all the work of

(setf (symbol-function name) (coerce (list 'lambda () form) 'function))

for you, with the added benefit of compiling the function, too. Rainer's answer shows how it can be used, and I think it's the most elegant solution to this problem.