Why does funcall ignore the lexical scope?

2019-09-04 17:45发布

问题:

I'm piqued by the code in this old answer explaining why sharp quote is needed, but what I don't understand is why funcall seems to skip the usual scope rules.

(defun test () 'red)

(flet ((test () 'green))
  (list (funcall 'test)
        (funcall #'test))) => (red green)

Should I understand Common Lisp as having both "local" lexically scoped symbol bindings as set by let-family functions and globally scoped variables symbol bindings as set by de- family functions?

回答1:

Common Lisp is assumed.

DEFUN and friends

DEFUN creates a global function binding, which can retrieved via symbols.

(defun foo () 'foo)

Above we have a function FOO.

Let's call it:

(funcall (function foo))   ; no lexical bound function available, so it uses
                           ; the symbol's binding

or

(funcall (symbol-function 'foo))

or

(funcall 'foo)

or

(foo)

All above access the same function.

Note: above shows that (foo) and (funcall 'foo) calls the same function. There is an exception: a file compiler may assume that a function FOO does not change. This allows a Lisp compiler to inline code or to compile to faster function calling code. Calling the function via the symbol as in (funcall 'foo) always results in a call to the current and latest binding - thus a lookup via the symbol is always needed.

FLET and LABELS

FLET and LABELS create lexically scoped function bindings. FUNCTION can reference such a binding. Note that these bindings can't be accessed via symbols at runtime. There are only two ways:

  • calling the function (foo).

  • referencing the function via (function foo).

Since both are using static lexical references, there is no lookup at runtime via symbols or similar. That means, symbols are not involved at runtime with lexical functions - they are only visible in the source code.

(flet ((foo () 'bar))   ; <- local lexical scope, function binding

   (foo)                          ; calls the lexical bound function foo

or

  (funcall (function foo))        ; calls the lexical bound function foo

but

  (funcall (symbol-function 'foo))   ; calls the symbol's binding,
                                     ; not the lexical binding

and

  (funcall 'foo)                     ; calls the symbol's binding
                                     ; not the lexical binding

)      


回答2:

This doesn't actually have much to do with funcall, but rather the difference between quote and function. Try it again without the funcall:

(defun test () 'red)

(flet ((test () 'green))
  (list 'test #'test)) => (TEST #<FUNCTION (FLET TEST) {C14D26D}>)

One of them is a symbol, and the other is a function object - the (lexically bound) function value of test. As you can see, quote returns its argument (without evaluating it) - that's where the lexical scope is ignored.

Once you understand this difference, it should be fairly clear why funcall behaves as it does in this example (at least, if you understand how funcall operates on symbols - see the hyperspec entry).



回答3:

The funcall function takes a function designator (a function object or a symbol). With #'foo you retrieve the function object bound to foo, in the lexical context this is evaluated. With 'foo, you create the symbol foo.

When funcall then maps the function designator to a function, it's either simply the identity (if you pass a function object, this is used) or has to be looked up in the global environment.