Search code examples
common-lisp

In Common Lisp is there a less verbose way to avoid taking a closure of a changing variable?


I have a function that takes a function and returns a new function. This code collects a list of the returned functions, where each is slightly different, using a different i value to modify how the function behaves. As it is written it doesn't work properly.

(loop for i from 0 to 5
    collect (get-graphing-fn (lambda (x) (sin (+ x (* i 1))))))

In it's current form the lambda I send to get-graphing-fn takes a closure of the index i causing each collected function to have the same numerical i value when they are called, which isn't what I want.

For the code to do what I want I need to create a new varaible for each iteration, so that the variable I am closing over does not change.

(loop for i from 0 to 5
    collect (let ((index i)) (get-graphing-fn (lambda (x) (sin (+ x (* index 1)))))))

But to me it seems quite verbose. I just want to get the value of i instead of taking a reference. Is there a more elegant way to achieve my goal?


Solution

  • As you've said, what you need to do is to capture a distinct binding of i for each function you create, while loop creates a single binding and mutates it. You do need to say, somehow, which bindings you wish to create as otherwise the obviously correct thing to do is to capture the binding of i created by loop.

    The most concise way of doing this without extending the language is probably what you've done. But the entire purpose of Lisp is to build the language you want, so here is an extension to the language (this is not technically conforming CL because μ is not in the standard character set):

    ;;; Pick to taste.  you can't use this symbol as an argument or bind
    ;;; it in the arguments to μ
    ;;;
    (defconstant μ-sep '/)
    
    (defmacro μ (args/bindings &body forms)
      (multiple-value-bind (args bindings)
          ;; Pick separator to choice
          (let ((p (position μ-sep args/bindings)))
            (if p
                (values (subseq args/bindings 0 p)
                        (subseq args/bindings (1+ p)))
              (values args/bindings '())))
        `(let ,(mapcar (lambda (binding)
                         (etypecase binding
                           (symbol `(,binding ,binding))
                           (cons binding)))
                       bindings)
           (lambda ,args ,@forms))))
    

    And now:

    > (mapcar #'funcall
              (loop for i from 0 to 5
                    collect (μ (x / i) (+ x i)))
              '(3 1 4 1 5 9))
    (3 2 6 4 9 14)
    

    I can't think of a more concise way of doing this than this: you do have to indicate the bindings you wish to create somehow. in order that they be created.