Пожалуйста, объясните некоторые из баллов Павла Грэма на Lisp

StackOverflow https://stackoverflow.com/questions/2710231

Вопрос

Мне нужна помощь понять некоторые очки от Пола Грэма Что сделал Лисс другим.

  1. Новая концепция переменных. В Lisp все переменные являются эффективно указателями. Значения - это то, что имеют типы, не переменные, а присвоение или обязательные переменные означает копирование указателей, а не то, что они указывают.

  2. Тип символа. Символы отличаются от строк в том, что вы можете проверить равенство, сравнивая указатель.

  3. Обозначение для кода с использованием деревьев символов.

  4. Весь язык всегда доступен. Там нет реального различия между временем чтения, компиляции и времени выполнения. Вы можете компилировать или запустить код во время чтения, чтения или запуска кода во время компиляции, и чтения или компиляционного кода во время выполнения.

Что означают эти точки? Как они отличаются на языках, таких как C или Java? Есть ли другие языки, отличные от семейных языков Lisp, сейчас есть какие-либо из этих конструкций?

Это было полезно?

Решение

Объяснение Мэтта совершенно хорошо - и он берет на себя выстрел по сравнению с 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 и заканчивается на ) далеко вправо. Это Lexed / Parsed, как ожидают, но уже возникает важная точка: Результатом является не какое-то специальное представление, специфичное для компилятора AST - это просто обычная структура данных Clojure / Lisp, а именно вложенный список, содержащий кучу символов, строк и - в этом случае - один компилированный объект Regex Pattern, соответствующий #"\d+" буквальный (больше на этом ниже). Некоторые удары добавляют свои маленькие повороты к этому процессу, но Пол Грахам в основном имел в виду общий Lisp. На точках, относящихся к вашему вопросу, Clojure похож на CL.

Весь язык во время компиляции:

После этого точки все компиляторы имеют дело с (это также будет верно для переводчика Lisp; Clojure Code всегда собирается) - это структуры данных Lisp, которые используются программисты, которые используются для манипулирования. В этот момент становится замечательной возможностью: почему бы не разрешить программистам Lisp записывать функции LISP, которые манипулируют данные LISP, представляющие программы Lisp и преобразованные данные, представляющие преобразованные программы, которые будут использоваться вместо оригиналов? Другими словами - почему не позволяют программистам Lisp регистрировать свои функции как плагины компилятора сортов, называемые макросами в Lisp? И действительно какая-то приличная система LISP имеет эту емкость.

Таким образом, макросы являются регулярными функциями LISP, работающие на представлении программы во время компиляции, до окончательной фазы компиляции, когда испускается фактический объектный код. Поскольку нет никаких ограничений на виды кода MACROS разрешено работать (в частности, код, который они бегите, часто пишутся с либеральным использованием макросъемки), можно сказать, что «весь язык доступен при компиляции времени ".

Весь язык по времени чтения:

Давайте вернемся к этому #"\d+" Regex Litalal. Как уже упоминалось выше, это преобразуется в фактический компиляционный объект шаблона при чтении времени, прежде чем компилятор слышит первое упоминание о новом подготовленном к составлению компиляции. Как это произошло?

Что ж, как в настоящее время реализован путь Clojure, картина несколько отличается от того, что имел в виду Пол Грэм, хотя что-то возможно с Умный взлом. Отказ В общем Lisp история была бы немного уборщина концептуально. Однако основы являются аналогичными: читатель Lisp - это станок, который в дополнение к выполнению переходов состояния и в конечном итоге объявляет, достигло ли он «принятие состояния», выделяет структуры данных LISP. Таким образом, персонажи 123 стать числом 123 И т.д. Важная точка приходит сейчас: Этот государственный аппарат может быть изменен пользовательским кодом. Отказ (Как отмечалось ранее, это совершенно верно в деле Cl; для Clojure, взломать (обескураженный и не использованный на практике). Но я отвлечет, это статья PG, я должен разрабатывать, так ...)

Итак, если вы общий программист LISP, и вы, понравились, как идея векторальных литералов в стиле Clojure, вы можете просто подключить к функции Reader функцию для надлежащего реагирования на некоторую последовательность символов - [ или #[ возможно - и относитесь к этому как начало вектора буквального заканчивания на соответствующем ]. Отказ Такая функция называется Reader Macro. И просто как обычный макрос, он может выполнять любой код LISP, включая код, который сам был написан с помощью функции Funky, включенным ранее зарегистрированным макросом читателя. Так что есть весь язык в чтении для вас.

Упаковка в него:

На самом деле, что было продемонстрировано до сих пор, что можно запускать регулярные функции Lisp при чтении времени или компиляции; Один шаг нужно принять отсюда, чтобы понять, как чтение и компиляция сами возможны, при чтении, компиляции или времени выполнения - это реализовать, что чтение и компиляция сами по себе выполняется функциями Lisp. Вы можете просто позвонить read или eval В любое время для чтения данных Lisp из потоков символов или Completion & Execute Code Lisp соответственно. Это весь язык прямо там, все время.

Обратите внимание, как тот факт, что Lisp удовлетворяет точке (3) из вашего списка, имеет важное значение для того, как он удается удовлетворить точку (4), - особый аромат макросов, предоставляемых Lisp, в значительной степени зависит от кода, представленного регулярными данными Lisp, что что-то включено в (3). Кстати, только аспект «дерева» кода действительно имеет решающее значение - вы можете предположить, что у вас есть Lisp, написанный с помощью XML.

Другие советы

1) Новая концепция переменных. В Lisp все переменные являются эффективно указателями. Значения - это то, что имеют типы, не переменные, а присвоение или обязательные переменные означает копирование указателей, а не то, что они указывают.

(defun print-twice (it)
  (print it)
  (print it))

«Это» - это переменная. Это может быть связано с любым значением. Нет ограничений и ни одного типа, связанного с переменной. Если вы вызываете функцию, аргумент не нужно копировать. Переменная аналогична указателю. У него есть способ получить доступ к значению, связанным с переменной. Нет необходимости резерв Память. Мы можем пройти любой объект данных, когда мы вызываем функцию: любой размер и любой тип.

Объекты данных имеют «тип», и все объекты данных могут быть запрошены для его «типа».

(type-of "abc")  -> STRING

2) Тип символа. Символы отличаются от строк в том, что вы можете проверить равенство, сравнивая указатель.

Символ - это объект данных с именем. Обычно имя можно использовать для поиска объекта:

|This is a Symbol|
this-is-also-a-symbol

(find-symbol "SIN")   ->  SIN

Поскольку символы являются реальными объектами данных, мы можем проверить, являются ли они того же объекта:

(eq 'sin 'cos) -> NIL
(eq 'sin 'sin) -> T

Это позволяет нам например, чтобы написать предложение с символами:

(defvar *sentence* '(mary called tom to tell him the price of the book))

Теперь мы можем подсчитать количество в предложении:

(count 'the *sentence*) ->  2

В общих символах Lisp не только имеет имя, но они также могут иметь значение, функцию, список свойств и пакет. Таким образом, символы могут использоваться для названия переменных или функций. Список свойств обычно используется для добавления метаданных в символы.

3) Обозначение для кода с использованием деревьев символов.

Lisp использует свои основные структуры данных для представления кода.

Список (* 3 2) может быть как данные, так и код:

(eval '(* 3 (+ 2 5))) -> 21

(length '(* 3 (+ 2 5))) -> 3

Дерево:

CL-USER 8 > (sdraw '(* 3 (+ 2 5)))

[*|*]--->[*|*]--->[*|*]--->NIL
 |        |        |
 v        v        v
 *        3       [*|*]--->[*|*]--->[*|*]--->NIL
                   |        |        |
                   v        v        v
                   +        2        5

4) Весь язык всегда доступен. Там нет реального различия между временем чтения, компиляции и времени выполнения. Вы можете компилировать или запустить код во время чтения, чтения или запуска кода во время компиляции, и чтения или компиляционного кода во время выполнения.

Lisp предоставляет функции, прочитанные для чтения данных и кода из текста, нагрузки в код загрузки, Eval, чтобы оценить код, компилировать код компиляции и печати для записи данных и кода в текст.

Эти функции всегда доступны. Они не уходят. Они могут быть частью любой программы. Это означает, что любая программа может прочитать, загружать, Eval или Print Code - всегда.

Как они отличаются на языках, таких как C или Java?

Эти языки не предоставляют символы, код как данные или оценка времени выполнения данных в качестве кода. Объекты данных в C обычно неразъедны.

Есть ли другие языки, отличные от семейных языков Lisp, сейчас есть какие-либо из этих конструкций?

У многих языков есть некоторые из этих возможностей.

Разница:

В Lisp эти возможности разработаны на языке, чтобы их было легко в использовании.

Для очков (1) и (2) он говорит исторически. Переменные Java в значительной степени одинаковы, поэтому вам нужно позвонить. Equals () для сравнения значений.

(3) говорит о S-выражениях. Программы LISP записываются в этом синтаксисе, что обеспечивает множество преимуществ по сравнению с Ad-Hoc Syntax, как Java и C, такие как захватывающие повторные шаблоны в макросах в виде намного очистителя, чем C MACROS или C ++, а также манипулирующий код с одинаковым списком операции, которые вы используете для данных.

(4) Принимая C Например: язык - это действительно два разных подстраивания: прочее, как (), а пока (), а также препроцессор. Вы используете препроцессор для сохранения необходимости повторять себя все время или пропустить код с помощью #, если / # IFDEF. Но оба языка довольно разделяются, и вы не можете использовать во время () во время компиляции, как вы можете #if.

C ++ делает это еще хуже с шаблонами. Ознакомьтесь с несколькими ссылками на шаблон MetaProgramming, что обеспечивает способ генерирования кода во время компиляции и чрезвычайно сложно для не экспертов обернуть головы. Кроме того, это действительно куча хаков и трюков, использующих шаблоны и макросы, которые компилятор не может предоставить первоклассную поддержку - если вы делаете простой синтаксис ошибки, компилятор не может дать вам четкое сообщение об ошибке.

Ну, с Lisp, у вас есть все это на одном языке. Вы используете те же вещи для генерации кода во время выполнения, когда вы учитесь в свой первый день. Это не нужно предлагать MetaProgramming, тривиально, но это, безусловно, более простым с первым классовым языком и поддержкой компилятора.

Очки (1) и (2) также подходят бы Python. Простой пример «A = STR (82.4)» переводчик сначала создает объект плавающего точка со значением 82.4. Затем он вызывает строковый конструктор, который затем возвращает строку со значением '82 .4 '. «A» на левой стороне - просто метка для этого строкового объекта. Оригинальный объект с плавающей запятой был сборщик мусора, потому что нет никаких ссылок на него.

На схеме все рассматривается как объект аналогичным образом. Я не уверен в общих лиспе. Я бы попытался избежать мышления с точки зрения концепций C / C ++. Они замедлили меня куча, когда я пытался вернуть голову вокруг прекрасной простоты удаления.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top