1

When working on a personal project, I found what seems to be an inconsistent behaviour of closures.

The idea in the codes below is to find the maximum within several numbers (1, 3, 2, 5, 4) by inspecting them one after the other. The maximum is obviously 5. The codes below seem sophisticated, since they are minimal examples derived from a more complicated case.

Code 1, with plain numbers --> it works

(defun look-for-maximum-1 ()

  (labels ((test-successive-values-with-function (f)
         (let ((z 0))
           (setf z 1)
           (funcall f z)
           (setf z 3)
           (funcall f z)
           (setf z 2)
           (funcall f z)
           (setf z 5)
           (funcall f z)
           (setf z 4)
           (funcall f z))))

    (let ((maximum -1))

      (test-successive-values-with-function

       ;; function to check if a given number is the new maximum:
       (lambda (x)
     (format t "~&Inspecting ~S whereas maximum is ~S" x maximum)
     (when (> x maximum)
       (format t "~&Maximum was:    ~S" maximum)
       (setf maximum x)
       (format t "~&Maximum is now: ~S" maximum))))

      (format t "~&Final maximum: ~S" maximum))))

The call to (look-for-maximum-1) brings the following output:

Inspecting 1 whereas maximum is -1

Maximum was: -1

Maximum is now: 1

Inspecting 3 whereas maximum is 1

Maximum was: 1

Maximum is now: 3

Inspecting 2 whereas maximum is 3

Inspecting 5 whereas maximum is 3

Maximum was: 3

Maximum is now: 5

Inspecting 4 whereas maximum is 5

Final maximum: 5

This output is correct: the program progressively finds the maximum (which is 5).

Code 2 = the same with numbers encapsulated in one-element lists --> it does not work

(defun look-for-maximum-2 ()

  (labels ((compare-car (list1 list2)
         "Returns true if and only if the car of list1 is > than car of list2"
         (> (car list1) (car list2)))

       (test-successive-values-with-function (f)
         (let ((z '(0)))
           (setf (car z) 1)
           (funcall f z)
           (setf (car z) 3)
           (funcall f z)
           (setf (car z) 2)
           (funcall f z)
           (setf (car z) 5)
           (funcall f z)
           (setf (car z) 4)
           (funcall f z))))

    (let ((maximum '(-1)))

      (test-successive-values-with-function

       ;; function to check if a given number is the new maximum:
       (lambda (x)
     (format t "~&Inspecting ~S whereas maximum is ~S" x maximum)
     (when (compare-car x maximum)
       (format t "~&Maximum was:    ~S" maximum)
       (setf maximum x)
       (format t "~&Maximum is now: ~S" maximum))))

      (format t "~&Final maximum: ~S" maximum))))

The call to (look-for-maximum-2) brings the following output:

Inspecting (1) whereas maximum is (-1)

Maximum was: (-1)

Maximum is now: (1)

Inspecting (3) whereas maximum is (3)

Inspecting (2) whereas maximum is (2)

Inspecting (5) whereas maximum is (5)

Inspecting (4) whereas maximum is (4)

Final maximum: (4)

This is incorrect. The program seems to lose track of the maximum, whereas it is within the closure. The maximum seems systematically set to the value of the inspected number, whereas it should not be this way.

Any idea for the above-described strange behaviour?

The codes were tested with: - CLISP through Emacs+Slime - SBCL through Emacs+Slime - Common Lisp on line: https://www.tutorialspoint.com/execute_lisp_online.php

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
Nicolas56
  • 166
  • 2
  • 4
  • You are mutating quoted data, ie. literal data. This is a big No in CL, because the behavior is unspecified: `(setf (car z) ...)` where z is a constant list. See http://stackoverflow.com/q/8962909/124319 – coredump Feb 13 '17 at 21:00
  • First, don't modify quoted data. Use `(let ((z (list 0))...` instead of `(let ((z '(0)...`. The actual problem is that you're setting maximum to the same object that you're modifying in `test-successive-values-with-function`. Use `(setf maximum (copy-list x))` instead. – jkiiski Feb 13 '17 at 21:06
  • Thanks to both. The use of `copy-list`, as proposed by @jkiiski solved the problem. – Nicolas56 Feb 13 '17 at 21:10
  • Even when fixing immutable list you still override the binding instead of the `car` with `(setf maximum x)`. It should have been `(setf (car maximum) (car x))` since you did want to mutate the list and not the binding? – Sylwester Feb 13 '17 at 22:04

0 Answers0