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



Мне нужна помощь в понимании некоторых моментов от Пола Грэма Что Сделало Lisp Другим.




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


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


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


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



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

778   4  

4 ответов:

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

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

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

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

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

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

Ну, как Clojure в настоящее время реализуется, картина несколько отличается от того, что Пол Грэм имел в виду, хотя все возможно с умный Хак. совместно Лисп, история была бы немного чище концептуально. Основы, однако, похожи: читатель Lisp-это конечный автомат, который, помимо выполнения переходов состояний и в конечном итоге объявления о том, достиг ли он "принимающего состояния", выплевывает структуры данных Lisp, которые представляют символы. Таким образом, символы 123 стать номером 123 etc. Теперь наступает важный момент:этот конечный автомат может быть изменен пользовательским кодом. (Как отмечалось ранее, это полностью верно в случае CL; для Clojure требуется Хак (обескураженный и не используемый на практике). Но я отвлекся, это статья PG, которую я должен был бы развивать, так что...)

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

подводим итоги:

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

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

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

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

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

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

(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

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

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 для оценки кода, компиляции для компиляции кода и печати для записи данных и кода в текст.

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

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

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

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

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

разница:

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

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

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

(4) С "С", например: язык-это действительно два разных подгруппах языки: вещи, как если() и while(), и препроцессор. Вы используете препроцессор, чтобы избежать необходимости повторять себя все время, или пропустить код с #if/#ifdef. Но оба языка совершенно разные, и вы не можете использовать while() во время компиляции, как вы можете #if.

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

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

точки (1) и (2) также подходят для Python. Взяв простой пример" a = str(82.4) " интерпретатор сначала создает объект с плавающей запятой со значением 82.4. Затем он вызывает строковый конструктор, который затем возвращает строку со значением '82.4'. "А" на левой стороне-это просто ярлык для объекта String. Исходный объект с плавающей запятой был собран мусор, потому что на него больше нет ссылок.

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

Comments

    Ничего не найдено.