Lisp меняет функцию на лямбда-выражение при сохранении в ячейке функции - PullRequest
0 голосов
/ 16 февраля 2020

В этом сообщении я спрашиваю, почему, когда я объявляю в SBCL

(defun a (&rest x)
    x)

, а затем проверяю, что содержит ячейка функции

(describe 'a)
COMMON-LISP-USER::A
  [symbol]

A names a compiled function:
  Lambda-list: (&REST X)
  Derived type: (FUNCTION * (VALUES LIST &OPTIONAL))
  Source form:
    (LAMBDA (&REST X) (BLOCK A X))

Я вижу это конкретная разбивка оригинальной функции. Может ли кто-нибудь объяснить, что означает этот вывод? Меня особенно смущает последняя строка

 Source form:
        (LAMBDA (&REST X) (BLOCK A X))

Это таинственно, потому что по какой-то непонятной мне причине Лисп превратил исходную функцию в лямбда-выражение. Также было бы хорошо узнать подробности о том, как вызывается такая функция, как эта. Этот пример - SBCL. В Элиспе

(symbol-function 'a)

дает

(lambda (&rest x) x)

снова, странно. Как я сказал в другом посте, это легче понять в Схеме, но это создало путаницу в ответах. Итак, еще раз я спрашиваю: почему Лисп взял нормальное объявление функции и, похоже, сохранил его как лямбда-выражение?

Ответы [ 2 ]

2 голосов
/ 16 февраля 2020

Мне все еще немного непонятно, в чем вы смущены, но вот попытка объяснить это. Я буду придерживаться CL (и в основном ANSI CL), потому что у elisp есть много исторических странностей, которые только усложняют понимание (на elisp есть приложение). До ANSI CL также было гораздо менее понятно о различных вещах.

Я попытаюсь объяснить это с помощью написания макроса, который является простой версией defun: Назовите это defun/simple, и пример его использования будет

(defun/simple foo (x)
  (+ x x))

. Поэтому мне нужно выяснить, каким должно быть расширение этого макроса, чтобы он делал что-то в целом эквивалентное ( но проще, чем) defun.

Пространство имен функции & fdefinition

Прежде всего, я предполагаю, что вас устраивает мысль о том, что в CL (и elisp) пространство имен функций отличается от пространства имен привязок переменных: оба языка - это lisp-2. Таким образом, в форме, подобной (f x), f ищется в пространстве имен привязок функций, тогда как x ищется в пространстве имен привязок переменных. Это означает, что формы, подобные

(let ((sin 0.0))
  (sin sin))

, подходят для CL или elisp, тогда как в Scheme они будут ошибкой, поскольку 0.0 не является функцией, поскольку Scheme представляет собой lisp-1.

Итак, нам нужен какой-то способ доступа к этому пространству имен, а в CL самый общий способ сделать это - fdefinition: (fdefinition <function name>) получает определение функции <function name>, где <function name> это то, что называет функцию, которая для наших целей будет символом.

fdefinition - это то, что CL называет аксессором : это означает, что макрос setf знает, что с ним делать, так что мы можем изменить привязку функции символа на (setf (fdefinition ...) ...). (Это неправда: мы можем получить доступ и изменить с помощью fdefinition привязку символа верхнего уровня символа, мы не можем получить доступ или изменить привязки лексической функции, и CL не предоставляет никакого способа сделать это, но здесь это не имеет значения.)

Так что это говорит нам о том, как должно выглядеть наше расширение макроса: мы хотим установить определение (верхнего уровня) имени к некоторому объекту функции. Расширение макроса должно выглядеть следующим образом:

(defun/simple foo (x)
  x)

должно расширяться до чего-то, включающего

(setf (fdefinition 'foo) <form which makes a function>)

Так что мы можем написать этот бит макроса сейчас:

(defmacro defun/simple (name arglist &body forms)
  `(progn
     (setf (fdefinition ',name)
           ,(make-function-form name arglist forms))
     ',name))

Это полное определение этого макроса. Он использует progn в своем расширении, так что результатом расширения является имя определяемой функции, которое совпадает с defun: расширение выполняет всю свою реальную работу с побочным эффектом.

Но defun/simple опирается на вспомогательную функцию, называемую make-function-form, которую я еще не определил, поэтому вы на самом деле не можете использовать пока.

Формы функций

Итак, теперь нам нужно написать make-function-form. Эта функция вызывается во время макроразвлечения: ее задача не состоит в том, чтобы создать функцию: она возвращает немного исходного кода , который создаст функцию, которую я называю «формой функции».

Итак, как выглядят формы функций в CL? Ну, на самом деле в переносимом CL есть только одна такая форма (это может быть неправильно, но я думаю, что это правда), это форма, созданная с помощью специального оператора function. Поэтому нам нужно будет вернуть некоторую форму, которая выглядит как (function ...). Ну, что может быть ...? Существует два случая, когда function.

  • (function <name>) обозначает функцию, названную <name> в текущей лексической среде. Так что (function car) - это функция, которую мы вызываем, когда говорим (car x).
  • (function (lambda ...)) обозначает функцию, указанную (lambda ...): лямбда-выражением .

Вторым из них является only (предостережения, как указано выше), с помощью которого мы можем создать форму, обозначающую новую функцию. Так что make-function-form потребуется вернуть второй вариант формы function.

Итак, мы можем написать начальную версию make-function-form:

(defun make-function-form (name arglist forms)
  (declare (ignore name))
  `(function (lambda ,arglist ,@forms)))

И этого достаточно для defun/simple на работу:

> (defun/simple plus/2 (a b)
    (+ a b))
plus/2

> (plus/2 1 2)
3

Но это еще не совсем правильно: одна из вещей, которые могут сделать функции, определенные defun, - это возврат от себя: они знают свое имя и могут использовать return-from, чтобы вернуться из него:

> (defun silly (x)
    (return-from silly 3)
    (explode-the-world x))
silly

> (silly 'yes)
3

defun/simple пока не может этого сделать. Для этого make-function-form необходимо вставить подходящий block вокруг тела функции:

(defun make-function-form (name arglist forms)
  `(function (lambda ,arglist
               (block ,name
                 ,@forms))))

А теперь:

> (defun/simple silly (x)
    (return-from silly 3)
    (explode-the-world x))
silly

> (silly 'yes)
3

И все хорошо.

Это окончательное определение defun/simple и его вспомогательной функции.

Глядя на расширение defun/simple

Мы можем сделать это с помощью macroexpand обычным способом :

> (macroexpand '(defun/simple foo (x) x))
(progn
  (setf (fdefinition 'foo) 
        #'(lambda (x) 
            (block foo
              x)))
  'foo)
t

Единственное, что здесь сбивает с толку, это то, что, поскольку (function ...) распространен в исходном коде, есть синтаксис c сахар для него, который #'...: это та же самая причина, что quote имеет специальный синтаксис.

Стоит посмотреть на макроразложение реальных defun форм: в них обычно есть куча специфических для реализации c вещей, но вы можете найти то же самое там. Вот пример из LW:

 > (macroexpand '(defun foo (x) x))
(compiler-let ((dspec::*location* '(:inside (defun foo) :listener)))
  (compiler::top-level-form-name (defun foo)
    (dspec:install-defun 'foo
                         (dspec:location)
                         #'(lambda (x)
                             (declare (system::source-level
                                       #<eq Hash Table{0} 42101FCD5B>))
                             (declare (lambda-name foo))
                             x))))
t

Ну, здесь есть много лишних вещей, и у LW, очевидно, есть некоторая хитрость в этой форме (declare (lambda-name ...)), которая позволяет return-from работать без явного блока. Но вы можете видеть, что в основном происходит то же самое.

Вывод: как вы создаете функции

В заключение: нужен макрос типа defun или любая другая форма, определяющая функции развернуть в форму, которая при оценке создаст функцию. CL предлагает ровно одну такую ​​форму: (function (lambda ...)): - так вы создаете функции в CL . Поэтому что-то вроде defun обязательно должно расширяться до чего-то подобного. (Если быть точным: любая переносимая версия defun: реализации могут быть свободны для реализации-magi c и могут делать это. Однако они не свободны для добавления нового специального оператора.)

Что вы видите, когда вызываете describe, так это то, что после того, как SBCL скомпилировал вашу функцию, он вспомнил, какой была исходная форма, и исходная форма была именно той, которую вы получили бы из defun/simple здесь указан макрос.


Примечания

lambda как макрос

В ANSI CL lambda определяется как макрос, расширение которого подходит (function (lambda ...)) форма:

> (macroexpand '(lambda (x) x))
#'(lambda (x) x)
t

> (car (macroexpand '(lambda (x) x)))
function

Это означает, что вам не нужно писать (function (lambda ...)) самостоятельно: вы можете положиться на макроопределение lambda, делающее это за вас. Исторически, lambda не всегда был макросом в CL: я не могу найти свою копию CLtL1, но я почти уверен, что она там не была определена. Я вполне уверен, что определение макроса lambda появилось так, что было возможно писать ISLisp-совместимые программы поверх CL. Это должно быть на языке, потому что lambda находится в пакете CL, и поэтому пользователи не могут переносить макросы для него (хотя довольно часто они определяют такой макрос, или, по крайней мере, я). Я не использовал приведенное выше определение макроса.

defun/simple не претендует на то, чтобы быть правильным клоном defun: его единственная цель - показать, как можно написать такой макрос. В частности, это не касается объявлений должным образом, я думаю: они должны быть удалены из block и не являются.

Elisp

Elisp намного ужаснее, чем CL. В частности, в CL есть четко определенный тип function, который не пересекается со списками:

> (typep '(lambda ()) 'function)
nil

> (typep '(lambda ()) 'list)
t

> (typep (function (lambda ())) 'function)
t

> (typep (function (lambda ())) 'list)
nil

(Обратите внимание, в частности, что (function (lambda ())) является функцией, а не списком: function выполняет свою функцию по созданию функции.)

В elisp, однако, интерпретируемая функция - это просто список, машина которого равна lambda (предостережение: если лексическая привязка включена, это не тот случай: тогда это список, чья машина closure). Таким образом, в elisp (без лексической привязки):

ELISP> (function (lambda (x) x))
(lambda (x)
  x)

И

ELISP> (defun foo (x) x)
foo
ELISP> (symbol-function 'foo)
(lambda (x)
  x)

Затем интерпретатор elisp просто интерпретирует этот список, так же, как вы могли бы сами. function в elisp - это почти то же самое, что quote.

Но function не совсем то же самое, что и quote в elisp: байт-компилятор знает, что когда он сталкивается с формой, подобной (function (lambda ...)), это - форма функции, и он должен байтово-компилироваться тело. Итак, мы можем посмотреть на расширение defun в elisp:

ELISP> (macroexpand '(defun foo (x) x))
(defalias 'foo
  #'(lambda (x)
      x))

(Оказывается, defalias сейчас является примитивной вещью.)

Но если я поставлю это определение в файле, который я байт компилирую и загружаю, затем:

ELISP> (symbol-function 'foo)
#[(x)
  "\207"
  [x]
  1]

И вы можете исследовать это немного дальше: если вы поместите this в файл:

(fset 'foo '(lambda (x) x))

и затем байтовая компиляция и загрузка, затем

ELISP> (symbol-function 'foo)
(lambda (x)
  x)

Таким образом, байтовый компилятор ничего не сделал с foo, потому что он не получил подсказку, что он должен. Но foo все еще хорошая функция:

ELISP> (foo 1)
1 (#o1, #x1, ?\C-a)

Она просто не скомпилирована. Вот почему, если вы пишете код elisp с анонимными функциями, вы должны использовать function (или эквивалентно #'). (И, наконец, конечно, (function ...) правильно делает, если лексическая область видимости включена.)

Другие способы создания функций в CL

Наконец, я уже говорил выше, что function & конкретно (function (lambda ...)) - единственный примитивный способ создания новых функций в CL. Я не совсем уверен, что это правда, особенно учитывая CLOS (почти любой CLOS будет иметь своего рода экземпляры классов, которые являются функциями, но которые могут быть разделены на подклассы). Но это не имеет значения: это способ, и этого достаточно.

2 голосов
/ 16 февраля 2020

DEFUN является определяющим макросом. Макросы преобразуют код.

В Common Lisp:

(defun foo (a)
  (+ a 42))

Выше приведена форма определения, но она будет преобразована DEFUN в другой код.

Эффект аналогично

(setf (symbol-function 'foo)
      (lambda (a)
        (block foo
          (+ a 42))))

Выше устанавливается функциональная ячейка символа FOO для функции. Конструкция BLOCK добавляется SBCL, поскольку в Common Lisp именованные функции, определенные DEFUN, создают BLOCK с тем же именем, что и имя функции. Это имя блока может затем использоваться RETURN-FROM для включения нелокального возврата из указанной c функции.

Кроме того, DEFUN выполняет определенные вещи c. Реализации также записывают информацию о разработке: исходный код, местоположение определения и т. Д. c.

Схема имеет DEFINE:

(define (foo a)
  (+ a 10))

Это установит FOO для функционального объекта.

...