Ответ 1
Lisp - это широкое семейство языков и реализаций.
Динамический в контексте Lisp означает, что код имеет определенную гибкость во время выполнения. Его можно изменить или заменить, например.
Компиляция в Lisp
Часто реализации Lisp имеют компилятор, доступный во время выполнения. Когда этот компилятор является инкрементным, ему не нужны целые программы, но могут компилировать отдельные формы Lisp. Таким образом, мы вызываем компилятор для поддержки инкрементной компиляции.
Обратите внимание, что большинство компиляторов Lisp не являются компиляторами Just In Time. Вы как программист можете вызывать компилятор, например, в Common Lisp с функциями COMPILE
и COMPILE-FILE
. Затем скомпилируется код Lisp.
Кроме того, большинство Lisp систем как с компилятором, так и с интерпретатором позволяют свободно смешивать исполняемый и скомпилированный код.
В Common Lisp компилятор также может быть проинструктирован о динамическом компиляторе кода. Более продвинутый компилятор Lisp, такой как компилятор SBCL (или многие другие), может затем генерировать другой код.
Пример
(defun foo (a)
(bar a 3))
Выше функции foo
вызывает функцию bar
.
Если у нас есть функция bar
и переопределить ее, то мы ожидаем в Lisp, что новая функция bar
будет вызываться foo
. Нам не нужно перекомпилировать foo
.
Посмотрите GNU CLISP. Он компилируется в байтовый код. Это не родной машинный код, но для нашей цели здесь легче читать.
CL-USER 1 > (defun foo (a)
(bar a 3))
FOO
CL-USER 2 > (compile 'foo)
FOO
NIL
NIL
[3]> (disassemble #'foo)
Disassembly of function FOO
(CONST 0) = 3
(CONST 1) = BAR
1 required argument
0 optional arguments
No rest parameter
No keyword parameters
4 byte-code instructions:
0 (LOAD&PUSH 1)
1 (CONST&PUSH 0) ; 3
2 (CALL2 1) ; BAR
4 (SKIP&RET 2)
Поиск времени выполнения
Итак, вы видите, что вызов bar
выполняет поиск во время выполнения. Он смотрит на символ bar
, а затем вызывает функцию символа. Таким образом, таблица символов служит реестром для глобальных функций.
Этот просмотр в режиме исполнения в сочетании с инкрементным компилятором, доступный во время выполнения, позволяет нам генерировать код Lisp, компилировать его, загружать в текущую систему Lisp и модифицировать программу Lisp по частям.
Это делается с использованием косвенности. Во время выполнения система Lisp просматривает текущую функцию с именем bar
. Но обратите внимание: это не имеет ничего общего с компиляцией или интерпретацией. Если ваш компилятор компилирует foo
, а сгенерированный код использует этот механизм, то он является динамическим. Таким образом, у вас были бы служебные данные поиска как в интерпретированном, так и в компилированном коде.
С 70-х годов сообщество Lisp приложило много усилий, чтобы сделать семантику компилятора и интерпретатора как можно более похожими.
Язык, подобный Common Lisp, также позволяет компилятору сделать скомпилированный код менее динамичным. Например, не просматривая функции во время выполнения определенных частей кода.