Search code examples
lispcommon-lisp

How to call a flet function by symbol or string?


How to call a flet function by symbol or string?

((lambda (s) (flet ((fn1 (x) x)) (funcall s))) 'fn1)

and

((lambda (s) (flet ((fn1 (x) x)) (funcall s))) "fn1")

I know the above codes don't work because funcall needs a symbol, but I don't know how to get that symbol created inside a flet or let.


Solution

  • This answer has two parts:

    • why you can't do this;
    • why you can do this if you want to.

    In both parts I've not talked about why doing this sort of thing is terrible style, since it lets you ask questions, from outside, about the implementation details of functions: it should be no concern of anyone but the function what names it binds in its implementation.

    Why you can't do this

    I think it's worth thinking about why what you're trying to do really makes no sense. Here's your code with indentation to make it easier to read:

    ((lambda (s)
       (flet ((fn1 (x)
                x))
         (funcall s))
     'fn1)
    

    So what this is doing is calling the function (lambda (s) ...) with an argument of the symbol fn1. The function then tries to call whatever this symbol denotes as a function, and you expect this should be the function defined by the flet.

    OK, so let's consider something similar but without the whole local function thing

    ((lambda (s)
       (let ((g 1))
         (??? s))
       'g))
    

    So, the intention here is that this should return 1, because the function should be able to look up the value of the local binding for g by name.

    Well, there are two questions here:

    • what should the operator I called ??? be?
    • what would be the implications of this if it worked?

    What should ??? be? Well, clearly, it can't be a global function in a lexically-scoped language. So we'd need either for it to be bound as a local function (there is precedent for this in CL: call-next-method is like this), or for it to be a new special operator in the language: in either case I'll call this thing vbv (for 'variable binding value'). That operator does not exist in CL, by the way.

    What would the implications for the language be if vbv existed? Not good. For instance consider this function:

    (defun lookup (s)
      (let* ((x 1)
             (y (f x)))
        (vbv s)))
    

    Two things are apparent: this function has no idea what s is. So the function is restricted in two ways:

    • it has to maintain a table mapping symbols to bindings so vbv can work;
    • the binding of x can't be compiled away.

    This means that compilers for a language which had this operator would, in cases where it was used, necessarily suck. The only saving grace is that, since vbv can't be a global function it's possible to tell at compile time whether you have to do all this extra work or not: you can't do something like

    ((lambda (f s) 
       (let ((x 1))
         (funcall f s)))
     #'vbv 'x)
    

    which would make the whole situation completely hopeless. But that in turn is horrible: anyone who wants to write a compiler for such a language, and wants that compiler to produce good code, has to almost write two compilers: one crappy one which gets used for code where vbv appears, and one good one which gets used where it doesn't. No-one wants to be forced to do that.

    So that's why vbv does not exist.

    But local functions are just the same: I could invent another operator, fbv which gets at the locally-bound function-value of some symbol, and that has all the same problems that vbv does:

    • it can't be a global function (so, in particular, funcall or apply can't do this, because they are global functions);
    • its implications for the performance of code which uses it are horrible.

    Why you can do this, if you want to

    Well, this is Lisp: it's the programmable programming language. So of course, if you want to, you can do this.

    Here is a simple-minded version of a macro called named-labels which is like labels but maintains names. It is simple-minded because the local fbv function that this defines only looks at its own names, so nesting this does not work the way it should. To make that work I think you probably need a code-walker.

    (defun fbv (name)
      ;; function-binding-value, global version
      (declare (ignore name))
      (error "no bindings"))
    
    (defmacro named-labels (fbindings &body code)
      ;; like LABELS but make FBV work in the body for things we've
      ;; defined.
      (let ((stash-name (make-symbol "NAMED-LABELS-STASH")))
        `(let ((,stash-name '()))
           (labels ,fbindings
             ,@(loop for fb in fbindings
                     for n = (car fb)
                     collect `(push (cons ',n  (function ,n)) ,stash-name))
             (flet ((fbv (name)
                      (let ((found (assoc name ,stash-name)))
                        (if found
                            (cdr found)
                          (error "no binding for ~S" name)))))
               ,@code)))))
    

    And now

    > (funcall ((lambda (s)
                  (named-labels ((fn1 (x) x))
                    (fbv s)))
                'fn1)
               2)
    2
    

    Note that looking at the expansion of named-labels will show you why having the system do this for you by default would suck.