在 Common Lisp 中,如何使用词法范围和 funcall 使另一个函数作为参数传递?

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

我正在使用 SBCL、Emacs 和 Slime。因此,我可以这样做:

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

CL-USER> (funcall example 10)
200

好的。它按预期工作。使用库 Dexador,我也可以:

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}>

现在,我正在尝试使要传递的参数成为一个函数!更具体地说,dex:get 函数。我尝试了不同的方法,但 none 成功了:

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}>.

我设法做到了:

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"

但是,这感觉是不好的做法。有另一种方法来解决这个问题吗?

谢谢

我对你的问题感到困惑,虽然不像你看起来那么困惑。您似乎已经知道,要调用一个作为变量值的函数,您需要

  • funcall 如果你把所有的论点都当作单独的东西;
  • apply 如果你只有一个参数列表;

并且为了获得你需要的东西的函数值 (function thing) 或等效地 #'thing[1].

但是你在你的函数中忘记了这一点,也没有注意来自 SBCL 的大量警告。

所以

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

请注意,none 其中(您的问题中没有任何内容)依赖于词法范围:这在任何历史 Lisp 中都适用。


[1]:(lambda ...) 不需要 #',因为 lambda 是一个扩展为 (function (lambda ...)) 的宏。非常古老的代码有时会使用显式 #'(lambda ...) 形式,因为此宏并不总是存在于 CL 中。

您的代码:

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

这在 Common Lisp 中毫无意义。 x 是一个变量。您不能像 (x arg) 那样将变量用作函数。在 Common Lisp 中,函数和变量有不同的命名空间。例如LET引入局部变量,FLET引入局部函数。

调用绑定到变量的函数的方式有:

(funcall x arg)

(apply x (list arg))

因此正确的例子是:

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

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

你的解决方案是没有解决方案

这是你的例子:

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"

这并不像你想象的那样有效。

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

一样
(funcall hacky-eval "big html omitted")

然后

(eval "big html omitted")

然后

"big html omitted"

您对 eval 的所有调用都是对字符串本身求值。

您确实需要了解 Lisp 中的基本求值规则:

(defun foo (arg)
  (eval arg))

(foo (+ 3 4))

与:

完全相同
(defun foo (arg)
  arg)

(foo (+ 3 4))

相同
(identity (+ 3 4))

注意:如果您只将自我评估数据传递给 EVAL,那么它所做的只是 return 数据

函数调用 (foo (+ 1 2)) 的工作方式如下:

  1. Lisp 认为 FOO 是一个函数
  2. Lisp 计算参数。 (+ 1 2) -> 3
  3. Lisp 使用评估的参数调用函数 FOO:(funcall #'foo 3)
  4. Lisp 计算函数 FOO: (EVAL 3) -> 3
  5. Lisp returns 来自 FOO 的值 -> 3

首先使用正确的 defun:

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

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

现在您想使用 dex:get 以外的东西?好吧……写另一个函数,因为它们的参数处理,headers,return 值……可能不同。

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

也许在以后的代码中您想引用这两个函数?

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

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

这里你给一个匿名函数起一个名字...只需使用defun^^