В 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))работает так:

  1. Лисп видит, что FOO — это функция.
  2. Лисп оценивает аргументы. (+ 1 2) -> 3
  3. Lisp вызывает функцию FOO с оценочным аргументом:(funcall #'foo 3)
  4. Лисп вычисляет функцию FOO: (EVAL 3) -> 3
  5. 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^^

Другие вопросы по тегам