Question: Undefined function error inside LABELS in Common Lisp

Question

Undefined function error inside LABELS in Common Lisp

Answers 2
Added at 2016-12-20 08:12
Tags
Question

I have:

(defun serve (&key (port 80) (handler #'IDENTITY))
  (WITH-OPEN-SOCKET (socket :LOCAL-PORT port :LOCAL-HOST "localhost" :CONNECT :PASSIVE :REUSE-ADDRESS t)
    (LABELS 
      ((next-connection ()
       (with-open-stream (stream (ACCEPT-CONNECTION socket :wait t))
         (handler stream))
       (next-connection)))
      (next-connection))))

which generates warnings when entered into the REPL (Clozure Common Lisp):

;Compiler warnings :
;   In NEXT-CONNECTION inside SERVE: Undefined function HANDLER
;   In SERVE: Unused lexical variable HANDLER

Why is the handler variable unavailable?

And it's not just a warning, running it confirms the error:

(serve :port 6663 :handler #'do-stuff)

outputs:

Error: Undefined function HANDLER called with arguments (#<BASIC-TCP-STREAM ...>)

Shouldn't handler be available since it's lexically closed over the form?

Answers
nr: #1 dodano: 2016-12-20 09:12

I reduced your problem to this:

(defun serve (handler)
  (handler 'stream))

to show that it has nothing to do with labels (or the with-XXX macros).

The actual problem is that when Lisp evaluates a list, it looks at the first item in the list, expects it to be a symbol, looks up the function attribute of the symbol, and calls that function. (If you don't understand the "attributes" or "cells" of symbols, let us know and we'll expand more on that.)

This would work if handler had been defined with a defun, but because it is a "local variable" inside serve, you want Lisp to execute the function referenced by its value attribute.

To do this, you use Lisp's funcall function. The minimal example now becomes:

(defun serve (handler)
  (funcall handler 'stream))
  • or in your code (funcall handler stream)
nr: #2 dodano: 2016-12-20 10:12

You might want to format your code a bit more readable. This is how I would do it, using the editor for the indentation:

(defun serve (&key (port 80) (handler #'IDENTITY))
  (WITH-OPEN-SOCKET (socket
                     :LOCAL-PORT port
                     :LOCAL-HOST "localhost"
                     :CONNECT :PASSIVE
                     :REUSE-ADDRESS t)
     (LABELS ((next-connection ()
                (with-open-stream (stream (ACCEPT-CONNECTION socket :wait t))
                  (funcall handler stream))
                (next-connection)))
       (next-connection))))

Notice that you write a loop like you would do it in Scheme. In Common Lisp

(LABELS ((next-connection ()
           (with-open-stream (stream (ACCEPT-CONNECTION socket :wait t))
             (funcall handler stream))
           (next-connection)))
  (next-connection)

is just

(loop
 (with-open-stream (stream (ACCEPT-CONNECTION socket :wait t))
   (funcall handler stream)))

Advantages:

  • simpler and less code
  • no self recursive function needed
  • no tail call optimization needed to prevent stack overflows
  • less nesting in source code
  • explicit control flow construct makes the intent clear: here is a loop. The reader does not need to infer it from the code by trying to understand the function call sequence.

You could also make the connection code a function and still use a simple loop:

(flet ((handle-connection ()
         (with-open-stream (stream (ACCEPT-CONNECTION socket :wait t))
           (handler stream))))
  (loop (handle-connection)))
Source Show
◀ Wstecz