Search code examples
lispelisp

How to change lambda content dynamically in Emacs Lisp?


I have two functions that return an anonymous function, and almost all the content in the functions are the same except for a few lines.

(defun foo ()
  (interactive)
  (lambda (arg)
    (when (not (string-empty-p arg))
      (message "foo")
      (message arg))))

(defun bar ()
  (interactive)
  (lambda (arg)
    (when (not (string-empty-p arg))
      (message "bar")
      (message arg))))

Therefore, I created a function that received lists as argument and returned an anonymous function as follows:

(defun make-anonymous-func (&rest body)
  (interactive)
  (lambda (arg)
    `(when (not (string-empty-p arg))
      ,@body
      (message arg))))

However, when I executed the function returned by make-anonymous-func, the list in lambda was recognized as list literal and wasn't properly evaluated.

(funcall (make-anonymous-func '(message "foo")) "bar")
; ===> (when (not (string-empty-p arg)) (message "foo") (message arg))

Are there some good ways to make it work?
Thanks.


Solution

  • A form like

    `(when (not (string-empty-p arg))
          ,@body
          (message arg))
    

    is equivalent to something like this:

    (append '(when (not (string-empty-p arg)))
              body
              ((message arg))))
    

    So your function can be rewritten like this:

    (defun make-anonymous-func (&rest body)
      (lambda (arg)
        (append '(when (not (string-empty-p arg)))
                body
                `((message arg)))))
    

    And I think it's now very clear why this can't work. More generally, backquote forms provide a concise way of describing ways of constructing s-expressions by filling in templates, which is useful in macros, since s-expressions represent Lisp source code and macros are essentially functions whose values are source code, ad well as in other places where you want to construct s-expressions from templates.

    (One horrible approach to do what you want is eval which evaluates source code. There are many reasons why this is a very bad answer however, so I won't go into it here.)

    The general way to do what you're after is to rely on lexical scope. Conveniently elisp now has lexical scope, so you actually can do this. The following two examples assume that lexical-binding is true.

    First of all if you merely want to capture the value of some variable bindings then you can do this:

    (defun make-anonymous-func (msg)
      (lambda (arg)
        (when (not (string-empty-p arg))
          (message msg)
          (message arg))))
    

    The second approach is if you want the function to run some general code: the way to do this is to pass it a function to call:

    (defun make-anonymous-func (f)
      (lambda (arg)
        (when (not (string-empty-p arg))
          (funcall f arg)
          (message arg))))
    

    And now

    (funcall (make-anonymous-func
              (lambda (m)
                (message "foo")
                (sit-for 1)))
             "x")
    

    Will cause two messages to appear, but this time there will be a pause between them so you'll see the first.

    A final possibility, if what you want to do is create a bunch of similar functions statically – at compile time or definition time rather than dynamically at runtime – is to use a macro.

    (defmacro define-messaging-function (name args &rest forms)
      ;; elisp has no destructuring, so check
      (unless (and (listp args) (= (length args) 1)
                   (symbolp (car args)))
        (error "bad arguments"))
      (let ((arg (car args)))
        `(defun ,name (,arg)
           (when (not (string-empty-p ,arg))
             ,@forms
             (message ,arg)))))
    

    And now

    (define-messaging-function foo (x)
      (message "foo"))
    

    Defines foo as a function which, when called, will do what the function returned by your original foo did.