Search code examples
common-lispanonymous-functionlexical-scopefuncall

In Common Lisp, how to use lexical scope and funcall to make another function be passed as an argument?


I am using SBCL, Emacs, and Slime. Hence, I can do:

CL-USER> (defvar example #'(lambda (x) (* x 20)))
EXAMPLE

CL-USER> (funcall example 10)
200

Ok. It works as expected. Using the library Dexador, I can also so:

CL-USER> (ql:quickload :dexador)
To load "dexador":
  Load 1 ASDF system:
    dexador
; Loading "dexador"
.......
(:DEXADOR)

CL-USER> (dex:get "http://www.paulgraham.com")
"big HTML ommited"
200
#<HASH-TABLE :TEST EQUAL :COUNT 11 {10029F1443}>
#<QURI.URI.HTTP:URI-HTTP http://www.paulgraham.com>
#<SB-SYS:FD-STREAM for "socket 10.0.0.193:44936, peer: 74.6.52.135:80" {1002681F73}>

Now, I am trying to make the argument to be passed be a function! More specifically, the dex:get function. I tried different approaches, but none of them worked out:

CL-USER> (defvar example-failing #'(lambda (x) (x "http://www.paulgraham.com")))
; in: DEFVAR EXAMPLE-FAILING
;     (LAMBDA (X) (X "http://www.paulgraham.com"))
; 
; caught STYLE-WARNING:
;   The variable X is defined but never used.
; in: DEFVAR EXAMPLE-FAILING
;     (X "http://www.paulgraham.com")
; 
; caught STYLE-WARNING:
;   undefined function: COMMON-LISP-USER::X
; 
; compilation unit finished
;   Undefined function:
;     X
;   caught 2 STYLE-WARNING conditions
EXAMPLE-FAILING
CL-USER> (funcall example-failing dex:get)
; Evaluation aborted on #<UNBOUND-VARIABLE GET {1002C57103}>.
CL-USER> (funcall example-failing 'dex:get)
; Evaluation aborted on #<UNDEFINED-FUNCTION X {1002DEA263}>.
CL-USER> (funcall example-failing #'dex:get)
; Evaluation aborted on #<UNDEFINED-FUNCTION X {1002F906C3}>.
CL-USER> (funcall example-failing (function dex:get))
; Evaluation aborted on #<UNDEFINED-FUNCTION X {1003147F83}>.

I managed to do it with:

CL-USER> (defvar hacky-eval #'(lambda (x) (eval x)))
HACKY-EVAL
CL-USER> (funcall hacky-eval (dex:get "http://www.paulgraham.com"))
"big html omitted"

But, this feels as bad practice. Is there another way to fix this?

Thanks


Solution

  • Your code:

    (defvar example-failing
      #'(lambda (x)
         (x "http://www.paulgraham.com")))
    

    This makes no sense in Common Lisp. x is a variable. You can't use a variable as a function as in (x arg). In Common Lisp there are different namespaces for functions and variables. For example LET introduces a local variable and FLET introduces a local function.

    The ways to call a function bound to a variable are:

    (funcall x arg)
    
    (apply x (list arg))
    

    Thus correct examples would be:

    (defvar example-failing
      #'(lambda (x)
         (apply x (list "http://www.paulgraham.com"))))
    

    or

    (defvar example-failing
      #'(lambda (x)
         (funcall x "http://www.paulgraham.com")))
    

    Your solution is no solution

    This is your example:

    CL-USER> (defvar hacky-eval #'(lambda (x) (eval x)))
    HACKY-EVAL
    CL-USER> (funcall hacky-eval (dex:get "http://www.paulgraham.com"))
    "big html omitted"
    

    This does not work as you think.

    (funcall hacky-eval (dex:get "http://www.paulgraham.com"))
    

    is just the same as

    (funcall hacky-eval "big html omitted")
    

    and then

    (eval "big html omitted")
    

    and then

    "big html omitted"
    

    All your call to eval does is to evaluate a string to itself.

    You really need to understand basic evaluation rules in Lisp:

    (defun foo (arg)
      (eval arg))
    
    (foo (+ 3 4))
    

    is simply the same as:

    (defun foo (arg)
      arg)
    
    (foo (+ 3 4))
    

    which is the same as

    (identity (+ 3 4))
    

    Note: if you pass just self evaluating data to EVAL, then all it does is to return the data

    A function call (foo (+ 1 2)) works like this:

    1. Lisp sees that FOO is a function
    2. Lisp evaluates the arguments. (+ 1 2) -> 3
    3. Lisp calls the function FOO with the evaluated argument: (funcall #'foo 3)
    4. Lisp computes the function FOO: (EVAL 3) -> 3
    5. Lisp returns the value(s) from FOO -> 3