Homoiconic и "неограниченный" самоизменяющийся код + действительно ли lisp самоизменяется? - PullRequest
26 голосов
/ 13 декабря 2011

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

Я ищу примеры языков программирования, которые поддерживают как Homoiconicity (код имеет то же представление, что и данные), так и unrestricted self-модификация (Unrestricted, означающее, что вы можете изменять каждый аспект вашего работающего кода,не просто генерировать новый код или изменять указатели / делегаты функций.)

На данный момент я нашел только три примера, которые соответствуют этим критериям:

  1. Машинный код.Homoiconic в том, что все это число.Неограниченно модифицируется тем, что включает указатели, которые можно использовать для манипулирования любым адресом памяти независимо от того, содержит ли этот адрес код или данные.
  2. Malbolge.Те же рассуждения, что и машинный код.Каждая инструкция модифицируется после выполнения
  3. DNA.Не язык программирования, но все же интересно.Оно не самоизменяется в том же смысле, что и машинный код;Где фактические инструкции + данные изменены на месте.Однако он самовоспроизводится и может видоизменяться / эволюционировать в соответствии с его предыдущим состоянием (с такими побочными эффектами, как радиация, которые порой портят его).В любом случае, это всего лишь косвенный способ модификации себя.Короче говоря, ДНК может самоизменяться, но она делает это, воспроизводя себя в своей целостности вместе с соответствующими мутациями.Физическая строка ДНК является «неизменной».

Почему Лисп нет в этом списке

Лисп нет в этом списке, потому что мне кажется, чтоLisp только почти Homoiconic и поддерживает только ограниченную самодиагностику.Вы можете сделать что-то вроде

(+ 1 2 3)

, что будет делать то же самое, что и

(eval '(+ 1 2 3))

В первой версии (+ 1 2 3) является необработанным кодом, тогда как во второй версии это данные.Предполагая истинность этого утверждения, можно утверждать, что Лисп даже не омичен.Код имеет то же представление, что и данные, в том смысле, что они оба являются списками / деревьями / S-выражениями.Но тот факт, что вы должны явно пометить, какие из этих списков / деревьев / S-выражений являются кодом, а какие являются данными для меня, похоже, говорит о том, что Lisp в конце концов не омичен.Представления чрезвычайно похожи, но они отличаются мельчайшими деталями, которые вы фактически должны сказать, имеете ли вы дело с кодом или данными.Это ни в коем случае не плохо (на самом деле все остальное было бы безумием), но оно подчеркивает разницу между Лиспом и машинным кодом.В машинном коде вам не нужно явно отмечать, какие числа являются инструкциями, какие указатели, а какие данные.Все является просто числом, пока на самом деле не требуется интерпретация, и в этот момент это может быть любая из этих вещей.

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

'(+ 1 2 3)

на

'(+ 1 4 3)

, а затем выполните его через eval.Но когда вы делаете это, вы просто компилируете некоторый код и запускаете его.Вы не модифицируете существующий код, вы просто создаете и запускаете новый код.C # может делать то же самое, используя деревья выражений, даже если в менее удобном формате (что возникает из-за того, что код C # имеет другое представление для своего AST, в отличие от Lisp, который является его собственным AST),Можете ли вы взять весь исходный файл и начать изменять весь этот исходный файл во время его работы, при этом изменения, внесенные в исходный файл, влияют на поведение программы в реальном времени?

Если нет какого-либо способа сделать это, Лисп не является ни омиконическим, ни самоизменяющимся.(Чтобы отложить спор над определениями, Лисп не гомоичен и не самоизменяется до той же степени, что и машинный код. )

Способы сделать Лисп гомоиконическим / неограниченно самоограниченныммодифицируемый

Я вижу 3 возможных способа сделать Lisp гомо-звуковым / самоизменяемым как машинный код.

  1. Архитектура не фон Неймана. Если кто-то может изобрести какую-то удивительную гипотетическую машину, где представление программ самого низкого уровня - это AST, который может быть выполнен напрямую (дальнейшая компиляция не требуется).На такой машине AST будет представлять как исполняемые инструкции, так и данные.К сожалению, проблема не была решена, потому что AST все еще должен быть либо кодом, либо данными.Превосходство функции eval не меняет этого.В машинном коде вы можете переключаться между кодом и данными столько раз, сколько захотите.Принимая во внимание, что с помощью eval и Lisp, когда вы «утащили» какой-то список из данных в код и выполнили его, нет способа вернуть этот список обратно в качестве данных.Фактически, этот список исчез навсегда и был заменен его значением.Мы упустили бы что-то решающее, и именно так получились указатели.
  2. Ярлыки списков. Если бы требовалось, чтобы у каждого списка также был уникальный ярлык, было бы возможносделать косвенную самодиагностику, запустив функции для списка с данной меткой.В сочетании с продолжениями это, в конечном счете, позволило бы самостоятельно модифицировать код в том же смысле, в каком он есть в машинном коде.Метки соответствуют адресам памяти машинного кода.В качестве примера рассмотрим программу на Лиспе, где верхний узел AST имеет метку «main».Внутри main вы можете затем выполнить функцию, которая принимает метку, Integer, Atom и копирует атом в список с меткой, которая соответствует метке, предоставленной функции, по индексу, указанному в Integer.Тогда просто позвоните с текущим продолжением на главную.Вот, пожалуйста, самоизменяющийся код.
  3. Макросы Lisp. Я не нашел времени, чтобы разобраться с макросами Lisp, и на самом деле они могут делать именно то, о чем я думаю.

Точка 1. в сочетании с 2. создаст полностью самоизменяющийся Лисп.При условии, что описанная волшебная машина Лиспа может быть произведена2. один может создать самоизменяющийся Лисп, однако реализация на архитектуре фон Неймана может быть крайне неэффективной.

Вопросы

  1. Любыеязыки, отличные от машинного кода, днк и malbolge, которые могут выполнять полную самомодификацию и являются гомоиконическими?
  2. (НЕ ДУМАЙТЕ отвечать, если вы выполнили tl; dr по вышеуказанному тексту) .Действительно ли LISP гомоичны + самоизменяются?Если вы так говорите, можете ли вы процитировать точно, где в моем рассуждении я сбился с пути?

Приложение

Языки с неограниченной самодиагностикой, но без омиконичности

  1. Сборка.Код использует слова в отличие от чисел, поэтому теряет омиконичность, но в нем все еще есть указатели, которые сохраняют полный контроль над памятью и допускают неограниченное самостоятельное изменение.
  2. Любой язык, использующий необработанные указатели.Например, C / C ++ / Objective C. Тот же аргумент, что и в языках Assembly
  3. JIT, которые содержат виртуальные указатели.Например, C # /. Net работает в небезопасном контексте.Тот же аргумент, что и Assembly.

Другие понятия и языки, которые могут быть как-то актуальны / интересны:Lisp, Ruby, Snobol, Forth и его метапрограммирование во время компиляции, Smalltalk и его отражение, нетипизированное лямбда-исчисление с его свойством, что все является функцией (что предполагает, что мы можем изобрести машину, которая непосредственно выполняет лямбда-исчисление, лямбда-исчислениебыл бы гомоиконичным, а машинный код фон Неймана не был бы при запуске на указанной машине. [И теорема Годельса была бы исполняемой. Ха-ха, страшная мысль: P])

Ответы [ 4 ]

20 голосов
/ 13 декабря 2011

В первой версии (+ 1 2 3) является необработанным кодом, тогда как во второй версии это данные. Предполагая истинность этого утверждения, можно утверждать, что Лисп даже не омичен. Код имеет то же представление, что и данные, в том смысле, что они оба являются списками / деревьями / S-выражениями. Но тот факт, что вы должны явно указать, какие из этих списков / деревьев / S-выражений являются кодом, а какие являются данными для меня, похоже, говорит о том, что Лисп в конце концов не омичен.

Это не правда. В первой версии список (+ 1 2 3), который является данными, подается в интерпретатор, который должен быть выполнен, то есть должен быть интерпретируемым как код . Тот факт, что вы должны пометить s-выражения как код или данные в конкретном контексте , не делает Лисп негомоиконическим.

Смысл гомойконичности заключается в том, что все программы являются данными, а не то, что все данные являются программами, поэтому между ними все еще есть разница. В Лиспе (1 2 3) является допустимым списком, но не допустимой программой, поскольку целое число не является функцией.

[Если мы посмотрим на другой замечательный гомоиконический язык программирования, Пролог, то увидим то же явление: мы можем построить структуру данных foo(X, 1, bar), но без определения foo мы не сможем ее выполнить. Кроме того, переменные не могут быть именами предикатов или фактов, поэтому X. никогда не является допустимой программой.]

Лисп в значительной степени самоизменяется. Например, вот как можно изменить определение функции:

[1]> (defun foo (x) (+ x 1))
FOO
[2]> (defun bar (x) (+ x 2))
BAR
[3]> (setf (symbol-function 'foo) #'bar)
#<FUNCTION BAR (X) (DECLARE (SYSTEM::IN-DEFUN BAR)) (BLOCK BAR (+ X 2))>
[4]> (foo 3)
5

Объяснение: в [1] мы определили функцию foo как функцию add-1. В [2] мы определили bar как функцию add-2. На [3] мы сбрасываем foo на функцию add-2. На [4] мы видим, что мы успешно изменили foo.

11 голосов
/ 13 декабря 2011

Lisp - это семейство языков программирования. Члены этого семейства сильно различаются по своим возможностям и методам реализации. Есть две разные интерпретации этого:

  • Lisp - это семейство языков, которые имеют некоторые перекрывающиеся возможности. Это включает в себя все от первого Lisp до Maclisp, Scheme, Logo, Common Lisp, Clojure и сотни различных языков и их реализации.

  • В Lisp также есть основная ветвь языков, в названии которых также есть в основном "Lisp": Lisp, MacLisp, Common Lisp, Emacs Lisp, ...

Много исследований было вложено в течение долгого времени, чтобы улучшить языки (сделать их более «функциональными» или сделать их более объектно-ориентированными, или и то, и другое) и улучшить методы реализации.

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

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

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

В приложении на основе Common Lisp (скажем, в CAD-системе, написанной на Lisp), где большая часть исходной информации была удалена средством доставки, это было бы невозможно. Можно было бы иметь один исполняемый файл машинного кода, который лишен большей гибкости времени выполнения.

Гомойконичность также не очень хорошо определенная концепция. Для Lisp мне больше нравится то, что мы говорим, что исходный код можно превратить в данные, потому что исходный код использует внешнее представление s-выражений, которое является форматом сериализации данных. Но не каждое s-выражение является допустимой программой на Лиспе. Также внутреннее представление исходного кода в виде данных на Лиспе никоим образом не является «иконическим». Лисп имеет исходный код в виде внешних s-выражений и после прочтения исходного кода имеет внутреннее представление в виде данных Лисп. READ читает его, PRINT печатает его, а EVAL оценивает.

В Лиспе также есть другие подходы для предоставления доступа к Лиспу, на котором запущена ваша программа, и к программе:

  • протокол мета-объектов в CLOS (Common Lisp Object System) является таким примером

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

2 голосов
/ 24 февраля 2012

Я встречаюсь здесь как фанат, и я говорил это раньше, но прочитал Пола Грэма на Лиспе если вы хотите узнать о макросах Lisp. Они имеют большое значение с точки зрения разрешения изменений, которые в противном случае были бы невозможны. Кроме того, я думаю, что здесь важно различать семейство языков Lisp, данный Lisp и данную реализацию Lisp.

Я полагаю, что основная проблема, которую я принимаю в связи с вашим аргументом, возникает в первом абзаце после «Почему Lisp отсутствует в этом списке» и связана с REPL в Lisp. Когда вы вводите exp (+ 1 2 3) в интерпретатор, вы фактически вызываете функцию EVAL в списке (+ 1 2 3). «Необработанный код», который вы описываете, на самом деле является «данными», передаваемыми в другой код lisp, это просто данные в определенном контексте.

2 голосов
/ 15 декабря 2011

Макросы могут избегать цитирования, если вы ищете:

> (defmacro foo (x) (cdr x))
> (foo (+ - 5 2))
3

Код (+ - 5 2) или данные? Во время записи это похоже на данные. После расширения макроса это выглядит как код. И если бы определение foo было где-то еще, его можно было бы так же легко (неправильно) представить как функцию, и в этом случае (+ - 5 2) будет рассматриваться как код, который ведет себя как данные, которые ведут себя как код.

...