Объяснение Мэтта прекрасно - и он делает попытку сравнить с C и Java, чего я делать не буду, - но по какой-то причине мне очень нравится время от времени обсуждать именно эту тему, так что - вот мой снимок при ответе.
По пунктам (3) и (4):
Пункты (3) и (4) в вашем списке сейчас кажутся наиболее интересными и актуальными.
Чтобы понять их, полезно иметь четкое представление о том, что происходит с кодом Lisp - в виде потока символов, вводимых программистом - на пути к выполнению. Возьмем конкретный пример:
;; a library import for completeness,
;; we won't concern ourselves with it
(require '[clojure.contrib.string :as str])
;; this is the interesting bit:
(println (str/replace-re #"\d+" "FOO" "a123b4c56"))
Этот фрагмент кода Clojure распечатывает aFOObFOOcFOO. Обратите внимание, что Clojure, возможно, не полностью удовлетворяет четвертому пункту в вашем списке, поскольку время чтения на самом деле не открыто для пользовательского кода; Я все же буду обсуждать, что бы это значило, если бы это было иначе.
Итак, предположим, что у нас есть этот код где-то в файле, и мы просим Clojure выполнить его. Также предположим (для простоты), что мы прошли импорт библиотеки. Интересный фрагмент начинается с (println и заканчивается ) крайним правым. Это лексируется / анализируется, как и следовало ожидать, но уже возникает важный момент: результатом является не какое-то специальное представление AST, специфичное для компилятора - это просто обычная структура данных Clojure / Lisp, а именно вложенная список, содержащий набор символов, строк и - в данном случае - один скомпилированный объект шаблона регулярного выражения, соответствующий литералу #"\d+" (подробнее об этом ниже). Некоторые Lisp добавляют в этот процесс свои собственные небольшие повороты, но Пол Грэм в основном имел в виду Common Lisp. По вопросам, имеющим отношение к вашему вопросу, Clojure похож на CL.
Весь язык во время компиляции:
После этого все, с чем имеет дело компилятор (это также будет верно для интерпретатора Лиспа; код Clojure всегда компилируется), - это структуры данных Лиспа, которыми программисты на Лиспе привыкли манипулировать. В этот момент становится очевидной замечательная возможность: почему бы не позволить программистам на Лиспе писать функции на Лиспе, которые манипулируют данными Лиспа, представляющими программы на Лиспе, и выводят преобразованные данные, представляющие преобразованные программы, для использования вместо оригиналов? Другими словами - почему бы не позволить программистам на Лиспе регистрировать свои функции как своего рода подключаемые модули компилятора, называемые в Лиспе макросами? И действительно, любая приличная система Lisp обладает такой способностью.
Итак, макросы - это обычные функции Лиспа, работающие с представлением программы во время компиляции, перед финальной фазой компиляции, когда генерируется фактический объектный код. Поскольку нет ограничений на типы кода, которым разрешено запускать макросы (в частности, код, который они запускают, часто сам написан с широким использованием макросов), можно сказать, что весь язык доступен во время компиляции.
Весь язык во время чтения:
Вернемся к этому #"\d+" литералу регулярного выражения. Как упоминалось выше, он преобразуется в реальный скомпилированный объект шаблона во время чтения, прежде чем компилятор услышит первое упоминание о новом коде, который готовится к компиляции. Как это произошло?
Что ж, способ реализации Clojure в настоящее время несколько отличается от того, что имел в виду Пол Грэм, хотя все возможно с хитрый хак. В Common Lisp история была бы немного чище концептуально. Однако основы схожи: Lisp Reader - это конечный автомат, который, помимо выполнения переходов между состояниями и, в конечном итоге, объявления о том, достиг ли он принимающего состояния, выдает структуры данных Lisp, которые представляют символы. Таким образом, символы 123 становятся числом 123 и т. Д. Теперь наступает важный момент: этот конечный автомат может быть изменен с помощью кода пользователя. (Как отмечалось ранее, это полностью верно в случае CL; для Clojure требуется взлом (не рекомендуется и не используется на практике). Но я отвлекся, это статья PG, над которой я должен работать, так что ...)
Итак, если вы программист на Common Lisp и вам нравится идея векторных литералов в стиле Clojure, вы можете просто подключить к считывателю функцию, которая будет соответствующим образом реагировать на некоторую последовательность символов - возможно, [ или #[ - и обрабатывать это как начало векторного литерала, заканчивающегося совпадающим ]. Такая функция называется макросом чтения и, как и обычный макрос, может выполнять любой код Лиспа, включая код, который сам был написан с использованием фанковой нотации, разрешенной ранее зарегистрированными макросами чтения. Таким образом, для вас есть весь язык во время чтения.
Подводя итог:
Фактически, до сих пор было продемонстрировано, что можно запускать обычные функции Lisp во время чтения или компиляции; один шаг, который нужно сделать отсюда, чтобы понять, как чтение и компиляция сами по себе возможны во время чтения, компиляции или выполнения, - это осознать, что чтение и компиляция сами по себе выполняются функциями Lisp. Вы можете просто вызвать read или eval в любое время, чтобы прочитать данные Лиспа из символьных потоков или скомпилировать и выполнить код Лиспа соответственно. Вот и весь язык прямо здесь, все время.
Обратите внимание, как тот факт, что Lisp удовлетворяет пункту (3) из вашего списка, важен для того, как ему удается удовлетворить пункт (4) - особый вид макросов, предоставляемых Lisp, в значительной степени зависит от кода, представленного обычными данными Lisp, что разрешено (3). Между прочим, здесь действительно важен только древовидный аспект кода - возможно, вы могли бы написать Lisp с использованием XML.
person
Community
schedule
26.04.2010