How to call a referenced lambda?

Viewed 80

I'm trying to call a function (lambda) stored within an alist. Below is a small snippet that demonstrates what I'm trying to do:

(defvar *db* '((:add (lambda (a b)
                       (+ a b)))
               (:sub (lambda (a b)
                       (- a b)))))

(defun perform-operation-on-numbers (operation a b)
  "Performs specified operation on the supplied numbers."
  (let ((func (second (find operation
                            *db*
                            :key #'car))))
    ;; TODO: Call `func` on `a` and `b`
    (print func)))

(perform-operation-on-numbers :add 1 2)

No matter what I do, not even funcall is able to let me call the lambda stored against :add. How should I reference the retrieved lambda as a lambda?

3 Answers

Your use of quote lead to your inability to use funcall.

Look:

(setf *mydb* '((:add #'+)
               (:sub #'-)))
;; ((:ADD #'+) (:SUB #'-))

I can't use funcall. But:

(setf *mydb* (list (cons :add #'+)
                   (cons :sub #'-)))
;; ((:ADD . #<FUNCTION +>) (:SUB . #<FUNCTION ->))
;;
;;            ^^^^ "FUNCTION" ? That's better!          <----------
;;

I can (funcall (cdr (first *MYDB*)) 2)

Then the succinct notation is with back-quote and comma.

As pointed out by other answers, you are manipulating code as data, where the forms below (lambda ...) are unevaluated. But even with your data:

(defvar *db* '((:add (lambda (a b)
                       (+ a b)))
               (:sub (lambda (a b)
                       (- a b)))))

You can use funcall or apply, if you first use COERCE:

If the result-type is function, and object is a lambda expression, then the result is a closure of object in the null lexical environment.

For example, let's access the form associated with :add:

CL-USER> (second (assoc :add *db*))
(LAMBDA (A B) (+ A B))

The value is an unevaluated form. You can coerce it to a function:

CL-USER> (coerce (second (assoc :add *db*)) 'function)
#<FUNCTION (LAMBDA (A B)) {536B988B}>

Maybe you want to walk the terms to check that the lambda are only using a restricted set of operations, in which case it makes sense to keep them as data. But at some point you'll want to turn these code snippets to actual functions, and you can do that with coerce:

CL-USER> (defvar *db-fns*
           (loop 
             for (n c) in *db* 
             collect (list n (coerce c 'function))))
*DB-FNS*

Here you compute the functions once, and can reuse them later instead of calling coerce each time.

CL-USER> *db-fns*
((:ADD #<FUNCTION (LAMBDA (A B)) {536B9B5B}>)
 (:SUB #<FUNCTION (LAMBDA (A B)) {536B9C0B}>))

(it is equivalent to calling eval on the lambda form)

That's not a function: it's a list beginning (lambda ...). If you want a function have a function, for instance by

(defvar *db* `((:add ,(lambda (a b)
                        (+ a b)))
               (:sub ,(lambda (a b)
                        (- a b)))))

or, better, don't wrap the thing in some useless baggage:

(defvar *db* `((:add ,#'+
               (:sub ,#'-))
Related