Search code examples
lispcommon-lisp

How to call a referenced lambda?


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?


Solution

  • 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)