2

I need to make a function that generates functions like this:

(defun add3 (val) (+ 3 val))

It should be usable like this:

(setq adder7 nil)
(fset 'adder7 (make-adder 7))
(adder7 3)
     => 10

Ideally, function make-adder should return lambdas that do not have any symbols inside other then their parameter, for example:

(make-adder 7)
      => (lambda (val) (+ 7 val))

Update

I have tried following naive realisation:

(defun make-adder (n)
  (lambda (x) (+ n x)))

But that generates a lambdda that contains free symbol (not numeric constant!) and its usage fails.

(defalias 'add1 (make-adder 1))
(add1 2)
       => Debugger error void-variable n

(let ((n 5))
  (add1 2))
       => 7

Which is not what i want to get at all.

6
  • 1
    what's the actual problem you have with this task? What did you try? Commented Dec 20, 2015 at 22:08
  • Actual problem i have is: naive approach fails to work. That is, i get lambda containing free symbol which consequently fails to work Commented Dec 21, 2015 at 8:41
  • So this is in fact an Emacs Lisp question? I'll update the answer Commented Dec 21, 2015 at 8:51
  • 1
    Yes, I should have tagged is as such - common-lisp tag was added by someone else. Commented Dec 21, 2015 at 8:57
  • Does the answer to Emacs lisp: why does this sexp cause an invalid-function error? help here? Ultimately it comes down to the same thing: emacs's dynamic scoping. And I think that the answer there, using emacs's lexical-let might be a better solution that constructing a function with an injected constant value, at least in some contexts. Commented Dec 21, 2015 at 15:09

2 Answers 2

7

Emacs

Emacs relies on dynamic scoping by default. That's why the n symbol inside the returned lambda refers to an unbound variable. Either you toggle lexical scoping or you build a lambda form with the current value of n, as follows:

(defun make-adder (n)
  `(lambda (x) (+ ,n x)))

And then:

(defalias  'add1 (make-adder 1))
(add1 3)
=> 4 

Common Lisp

I originally thought the question was about Common Lisp (fset should have given me a hint), where you only have to do this:

(defun make-adder (n)
  (lambda (x) (+ n x)))

Your function takes a n and returns an anonymous function, which takes another parameter x and produces the result. But wait, make-adder is just a special case of partially applying some arguments to a function (see this question for details on the distinction between currying and partial application). The general approach to partially apply functions is:

(defun partial (function &rest partial-args)
  (lambda (&rest args)
    (apply function (append partial-args args))))

For example:

(let ((3+ (partial #'+ 3)))
  (funcall 3+ 7))
=> 10
Sign up to request clarification or add additional context in comments.

5 Comments

I must ask you for a reference on the syntax you have used. This seems to be just what i needed yet i never saw it in elisp manual
@Srv19 backquotes: gnu.org/software/emacs/manual/html_node/elisp/Backquote.html. And this question and its answers are quite informative.
Is the backquote solution better than using lexical-let to capture the variable? Since it's "read-only" in this case, I guess it'd be the same, but this weren't just an adder, but an accumulator or something, you'd want a closure over a binding, not a anonymous function with an injected constant value. See, e.g., this answer (disclaimer, it's mine) for an example. (Actually, that's the answer in which I first learned about emacs's lexical-let.)
@JoshuaTaylor You are right, I should have mentioned lexical-let. Somehow the answer evolved from being CL-only to Emacs+CL, and I am not really satisfied with it. I'll edit later when I have more time.
Well, in my use case function will be called exactly once but at undetermined moment in time, so there is no reason to capture a variable
2

coredump-'s answer address the right issue: emacs's dynamic scoping means that you no longer have access to the value of n that was in scope when your adder function was called. Rather than using backquote to construct a function with the constant injected, though, I think that it might be more descriptive to use emacs's lexical-let, since what you're looking for is a lexical closure. Just to be clear, here's the behavior you're getting now, with dynamic scoping:

(defun dyn-adder (n)
  (lambda (x)
    (+ n x)))

(funcall (dyn-adder 3) 5)
;; (void-variable n) error...

And here's how you can use lexical-let to get an actual lexical closure:

(defun lex-adder (n)
  (lexical-let ((n n))
    (lambda (x)
      (+ n x))))

(funcall (adder 3) 5)
;; 8

There isn't much difference between the backquote solution and lexical-let in defining a simple adder, but there are contexts where it's important to actually have a variable that you're referencing. E.g., if you wanted to create an accumulator, you'd need that local state. With dynamic scoping you'd get the same void-variable error:

(defun dyn-accumulator (n)
  (lambda (x)
    (incf n x)))

(let ((acc (dyn-accumulator 5)))
  (funcall acc 3)
  (funcall acc 8))
;; (void-variable n) error...

With a backquote approach, you'll get a different error. (I'm not sure I'm doing this correctly, though. I think that the error I'm getting is from trying to funcall a list, not from the function having a constant in it. But in any case, it should be clear that (lambda (x) (incf 5 x)) won't work, because you can't increment the constant value.)

(defun bq-accumulator (n)
  `(lambda (x)
     (incf ,n x)))

(let ((acc (bq-accumulator 5)))
  (funcall acc 3)
  (funcall acc 8))
;; wrong type argument error...

But with lexical-let, we have a real variable that we can modify:

(defun lex-accumulator (n)
  (lexical-let ((n n))
    (lambda (x)
      (incf n x))))

(let ((acc (lex-accumulator 5)))
  (funcall acc 3)
  (funcall acc 8))
;; 16

I learned about and described lexical-let more in an answer I wrote for Emacs lisp: why does this sexp cause an invalid-function error?; that may be useful information here, as well.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.