В Common Lisp, как использовать лексическую область видимости и funcall, чтобы передать другую функцию в качестве аргумента?
Я использую SBCL, Emacs и Slime. Следовательно, я могу сделать:
CL-USER> (defvar example #'(lambda (x) (* x 20)))
EXAMPLE
CL-USER> (funcall example 10)
200
Ok. Он работает так, как ожидалось. Используя библиотеку 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
функция. Я пробовал разные подходы, но ни один из них не сработал:
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"
Но это кажется плохой практикой. Есть ли другой способ исправить это?
Спасибо
3 ответа
Я смущен вашим вопросом, хотя и не так смущен, как вам кажется. Кажется, вы уже знаете, что для вызова функции, которая является значением переменной, вам нужно либо
-
funcall
если у вас все аргументы как отдельные вещи; -
apply
если у вас есть только список аргументов;
и это, чтобы получить значение функции чего-то, что вам нужно
(function thing)
или эквивалентно
#'thing
[1].
Но потом вы забываете об этом в своей функции и не обращаете внимания на обильные предупреждения от SBCL.
Так
(defvar *example* (lambda (f) (funcall f "http://www.paulgraham.com")))
...
(funcall *example* #'dex:get)
Обратите внимание, что ничто из этого (и ничего в вашем вопросе) не зависит от лексической области видимости: все это работало бы в любом историческом Лиспе.
[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 только данные для самостоятельной оценки, все, что он делает, это возвращает данные
Вызов функции
(foo (+ 1 2))
работает так:
- Лисп видит, что FOO — это функция.
- Лисп оценивает аргументы. (+ 1 2) -> 3
- Lisp вызывает функцию FOO с оценочным аргументом:(funcall #'foo 3)
- Лисп вычисляет функцию FOO: (EVAL 3) -> 3
- Lisp возвращает значение(я) из FOO -> 3
Начните с использования правильного :
(defun request (url)
(dex:get url))
CL-USER> (request "http://…")
Теперь вы хотите использовать что-то еще, кроме
dex:get
? Ну… напишите другую функцию, потому что их обработка аргументов, заголовки, возвращаемые значения… могут отличаться.
(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
^^