简体   繁体   中英

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

I am confused by your question although not as confused as you seem to be. You already seem to know that, to call a function which is the value of a variable, you need either

  • funcall if you have all the arguments as individual things;
  • apply if you have only a list of arguments;

and that to get the function value of something you need (function thing) or equivalently #'thing [1].

But then you forget that in your function, and pay no attention to the copious warnings from SBCL.

So

(defvar *example* (lambda (f) (funcall f "http://www.paulgraham.com")))
...
(funcall *example* #'dex:get)

Note that none of this (and nothing in your question) relies on lexical scope: this would all have worked in any historical Lisp.


[1]: you don't need #' for (lambda...) only because lambda is a macro which expands into (function (lambda...)) . Very old code sometimes uses the explicit #'(lambda...) form since this macro did not always exist in CL.

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

Start by using a proper defun :

(defun request (url)
  (dex:get url))

CL-USER> (request "http://…")

Now you want to use something else than dex:get ? Well… write another function, because their argument handling, headers, return values… might be different.

(defun request-drakma (url)
   (drakma:… url))

Maybe in later code you want to reference to either function?

(defun do-request (url &optional (get-fn #'request))
  (funcall get-fn url))

(defvar example #'(lambda (x) (* x 20)))

Here you are giving a name to an anonymous function… just use defun ^^

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM