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?
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))
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
(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.