The less-familiar parts of Lisp for beginners — function

Continuing our alphabetical tour through Lisp features that the newcomer might not have previously encountered, we arrive at function.

There are two common use cases for this special operator.  One is to interpret a function name in the current lexical environment and return the appropriate function object.  The other is to build lexical closures.

The first case was discussed some in the article on fboundp.  Recall that a function name defined by labels is not visible to called functions.  So, passing the name of a function defined with labels into, say, the predicate of the sort function will fail because sort will try to look up the function name in the set of interned symbols, and labels does not influence the set of interned symbols.  The function special operator allows the programmer to resolve the name in the lexical environment and use it to pass not the name of the function, but the actual function object itself.  Here is an example, first with incorrect syntax that raises an error:
 

(defun demonstrate-function ()
  (let ((input-list (list 10 2 4 9 5)))
    (labels
        ((even-odd-sort (v1 v2)
           "Sorts the values, but even numbers are always less than odd."
           (cond
             ((evenp v1)
              (or (oddp v2)
                  (< v1 v2)))
             (t
              (and (oddp v2)
                   (< v1 v2))))))

      (format t "Before sorting: ~A~%" input-list)
      (format t "After sorting: ~A~%" (sort input-list
                                            'even-odd-sort)))))

Running produces this:
 
CL-USER> (demonstrate-function)
Before sorting: (10 2 4 9 5)
; Evaluation aborted on #<UNDEFINED-FUNCTION EVEN-ODD-SORT {100544DAC3}>.

The reason this failed is that labels did not insert even-odd-sort into the set of interned symbols.  When the sort function received the symbol ‘even-odd-sort, it had no way to look up the function attached to that symbol.  In fact, if labels had been used to shadow an existing defun called even-odd-sort, then sort would have used the defun one, as that symbol would be interned.

The solution is to pass to sort the actual function code itself, rather than passing a  name and asking sort to look it up.  This is done with the function special operator:
 

(defun demonstrate-function ()
  (let ((input-list (list 10 2 4 9 5)))
    (labels
        ((even-odd-sort (v1 v2)
           "Sorts the values, but even numbers are always less than odd."
           (cond
             ((evenp v1)
              (or (oddp v2)
                  (< v1 v2)))
             (t
              (and (oddp v2)
                   (< v1 v2))))))

      (format t "Before sorting: ~A~%" input-list)
      (format t "After sorting: ~A~%" (sort input-list
                                            (function even-odd-sort))))))

Producing this output:
 
CL-USER> (demonstrate-function)
Before sorting: (10 2 4 9 5)
After sorting: (2 4 10 5 9)
NIL

Because the passing of function objects is a fairly common operation, there is a Lisp reader shorthand for this.  The code (function name) can be abbreviated #’name.

The second case, building a function closure, is demonstrated in the CLHS entry for function.  Here is it in use:
 

CL-USER> (defun adder (x)
           (function (lambda (y) (+ x y))))
ADDER
CL-USER> (let ((my-adder-10 (adder 10)))
           (format t "Calling my-adder-10 on the number 3: ~D"
                   (funcall my-adder-10 3)))
Calling my-adder-10 on the number 3: 13
NIL

This is, perhaps, a good place to talk a bit about closures.  The function adder returns a closure.  While the beginner might have run into that term, there is sometimes a tendency to think of a closure as a “function object”, but that entirely misses the meaning.  A closure is actually the lexical environment, the variables, that are available for the function(s) within it.  In this case, the closure retains the variable ‘x’.  Not the value of that variable, but the variable itself.  Functions contained within the closure can modify the variable, and those modifications are retained.  That is, the adder function above did not return a function that adds 10, it returned a function that adds ‘x’, and the closure holds the value of ‘x’, which happens to be 10 here.

One more bit about closures.  I once wrote some code which contained an error.  It was for starting a number of threads, and giving each of them its own index.  This was for doing some parallel matrix calculations, so different threads had to know which part of the matrix they were responsible for solving.  The code to start it up seemed simple enough, and I excerpt some of it here:
 

(defun spawn-thread (fcn &rest args)
  #+(and sbcl sb-thread) (sb-thread:make-thread #'(lambda ()
                                                    (apply fcn args)))
  #+(and sbcl (not sb-thread)) (error "Cannot call 'spawn-thread' because the environment does not support threads.")
  #-(or sbcl) (error "We need an implementation of thread starting for this platform"))

(defun start-workers ()
  (dotimes (j *number-of-worker-threads*)
    (push (threads:spawn-thread 'threaded-solver 
                                frame j 
                                *number-of-worker-threads*) 
          *thread-list*)))

This didn’t work.  Sometimes several threads all worked on the same block of the matrix, leaving parts of it unsolved.  They weren’t getting unique values of ‘j’.  The reason for this is that spawning the worker operates in two steps.  First, a new thread is spawned, and then the arguments are passed to the ‘threaded-solver function.  There is a race condition here, once the thread was spawned, it was waiting around to pass the value of ‘j’ to the worker function, but meanwhile the parent thread was still running, and it was eager to increment ‘j’.  The solution to this is to capture ‘j’ in a closure:
 

(defun start-workers ()
  (dotimes (j *number-of-worker-threads*)
    (let ((closure-j j))
      (push (threads:spawn-thread 'threaded-solver 
                                  frame closure-j
                                  *number-of-worker-threads*) 
            *thread-list*))))

With this change, the race condition disappeared.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

反垃圾邮件 / Anti-spam question * Time limit is exhausted. Please reload CAPTCHA.