20.1 Вычисление форм

Функция eval является главным пользовательским интерфейсом к вычислителю. Для пользовательских отладочных функций в интерпретаторе предусмотрены ловушки. Функции evalhook и applyhook предоставляют альтернативные интерфейсы к механизму вычислителя для использования этих отладочных возможностей.

[Функция] eval form

Форма form вычисляется в текущем динамическом окружении и нулевом лексическом. Что бы ни было вычислено, данное значение возвращается в качестве результата eval.

Следует отметить, что когда вы записываете вызов к eval, то для переданной формы происходят два уровня вычислений. Сначала происходит вычисление формы аргумента, как и любого аргумента для любой функции. Данное вычисление в свою очередь неявно вызывает eval. Затем происходит вычисление значения аргумента переданного в функцию eval. Например:

(eval (list ’cdr (car ’((quote (a . b)) c))))  b

Форма аргумента (list ’cdr (car ’((quote (a . b)) c))) вычисляется в (cdr (quote (a . b))). Затем eval вычисляет полученный аргумент и возвращает b.

Если всё, что требуется - это получить текущее динамическое значение для данного символа, то функция symbol-value может оказаться более эффективной, чем eval.

Пользователь ограничен в создании побочных действий так, как это описано в разделе 7.9


[Переменная] *evalhook*
[Переменная] *applyhook*

Если значение *evalhook* не является nil, тогда eval ведёт себя специальным образом. Не-/false значение *evalhook* должно быть функцией, которая принимает два аргумента, форму и окружение. Эта функция называется «функцией-ловушкой для вычислителя». Когда форма должна быть вычислена (любая, даже просто число или символ) неявно или явно с помощью eval, то вместо вычисления вызывается данная функция с формой в первом аргументе. Тогда функция-ловушка несёт ответственность за вычисление формы, и все что она вернёт будет расценено как результат вычисления этой формы.

Переменная *applyhook* похожа на *evalhook*, но используется, когда функция должна быть применена к аргументам. Если значение *applyhook* не nil, тогда eval ведёт себя специальным образом.

X3J13 voted in January 1989 to revise the definition of *applyhook*. Its value should be a function of two arguments, a function and a list of arguments; no environment information is passed to an apply hook function.

This was simply a flaw in the first edition. Sorry about that.

Когда функция должна примениться к списку аргументов, то вызывается функция-ловушка с данной функцией и списком аргументов в качестве параметров. Выполнение формы доверяется функции-ловушке. То, что она вернёт, будет расценено как результат вычисления формы. Функция-ловушка используется для применения обычных функций внутри eval. Она не используется для вызовов apply или funcall, таких функций, как map или reduce, или вызовов функций раскрытия макросов, таких как eval или macroexpand.

X3J13 voted in June 1988 to specify that the value of *macroexpand-hook* is first coerced to a function before being called as the expansion interface hook. This vote made no mention of *evalhook* or *applyhook*, but this may have been an oversight.

A proposal was submitted to X3J13 in September 1989 to specify that the value of *evalhook* or *applyhook* is first coerced to a function before being called. If this proposal is accepted, the value of either variable may be nil, any other symbol, a lambda-expression, or any object of type function.

Последний аргумент помещаемый в функции-ловушки содержит информацию о лексическом окружении в формате, который зависит от реализации. Эти аргументы одинаковы для функций evalhook, applyhook и macroexpand.

Когда вызывается одна из функций-ловушек, то обе переменные *evalhook* и *applyhook связываются со значениями nil на время выполнения данных функций. Это сделано для того, чтобы функция-ловушка не зациклилась. Функции evalhook и applyhook полезны для выполнения рекурсивных вычислений и применений (функции) с функцией ловушкой.

Функциональность ловушки предоставляется для облегчения отладки. Функциональность step реализована с помощью такой ловушки.

Если случается нелокальный выход на верхний уровень Lisp’а, возможно потому, что ошибка не может быть исправлена, тогда *evalhook* и *applyhook* в целях безопасности автоматически сбрасываются в nil.


[Функция] evalhook form evalhookfn applyhookfn &optional env
[Функция] applyhook function args evalhookfn applyhookfn &optional env

Функции evalhook и applyhook представлены для облегчения использования функциональности ловушек.

В случае evalhook вычисляется форма form. В случае applyhook функция function применяется к списку аргументов args. В обоих случаях, в процессе выполнения операции переменная *evalhook* связана с evalhookfn, и *applyhook* с applyhookfn. Кроме того, аргумент env используется для установки лексического окружения. По-умолчанию env установлен в нулевое окружение. The check for a hook function is bypassed for the evaluation of the form itself (for evalhook) or for the application of the function to the args itself (for applyhook), but not for subsidiary evaluations and applications such as evaluations of subforms. It is this one-shot bypass that makes evalhook and applyhook so useful. FIXME

X3J13 voted in January 1989 to eliminate the optional env parameter to applyhook, because it is not (and cannot) be useful. Any function that can be applied carries its own environment and does not need another environment to be specified separately. This was a flaw in the first edition.

Вот пример, очень простой функции трассировки, которая использует возможности evalhook.

(defvar *hooklevel* 0)

(defun hook (x)
  (let ((*evalhook* ’eval-hook-function))
    (eval x)))

(defun eval-hook-function (form &rest env)
  (let ((*hooklevel* (+ *hooklevel* 1)))
    (format *trace-output* "~%~V@TForm:  ~S"
            (* *hooklevel* 2) form)
    (let ((values (multiple-value-list
                     (evalhook form
                               #’eval-hook-function
                               nil
                               env))))
      (format *trace-output* "~%~V@TValue:~{ ~S~}"
              (* *hooklevel* 2) values)
      (values-list values))))

Используя эти функции можно, например, увидеть такую последовательность:

(hook ’(cons (floor *print-base* 2) ’b))
  Form: (CONS (FLOOR *PRINT-BASE* 2) (QUOTE B))
    Form: (FLOOR *PRINT-BASE* 3)
      Form: *PRINT-BASE*
      Value: 10
      Form: 3
      Value: 3
    Value: 3 1
    Form: (QUOTE B)
    Value: B
  Value: (3 . B)
(3 . B)


[Функция] constantp object

Если предикат constantp для объекта object истинен, то данный объект, когда рассматривается как вычисляемая форма, всегда вычисляется в одно и то же значение. Константные объекты включают самовычисляемые объекты, такие как числа, строковые символы, строки, битовые вектора, ключевые символы, а также символы констант, определённых с помощью defconstant, nil, t и pi. В дополнение, список, у которого car элемент равен quote, например (quote foo), также является константным объектом.

Если constantp для объекта object ложен, то этот объект, рассматриваемый как форма, может не всегда вычисляться в одно и то же значение.