Подтвердить что ты не робот

Homoiconic и "неограниченный" самомодифицирующийся код + Is lisp действительно самомодифицируется?

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

Поистине гомоиконические и самомодифицируемые языки

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

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

  • Машинный код. Homoiconic в том, что все - это число. Неограниченно модифицируемый, поскольку он включает указатели, которые могут использоваться для управления любым адресом памяти независимо от того, хранит ли этот адрес код или данные.
  • Malbolge. Те же рассуждения, что и машинный код. Каждая инструкция изменяет себя после выполнения
  • ДНК. Не язык программирования, но все же интересный. Это не самомодификация в том же смысле, что и машинный код; Если фактические инструкции + данные изменены на месте. Однако он сам реплицируется и может мутировать/развиваться в соответствии с ним в предыдущем состоянии (с побочными эффектами, такими как излучение, время от времени зависящее от него). В любом случае это косвенный способ самостоятельной модификации. Короче говоря, ДНК может самостоятельно модифицироваться, но она делает это, воспроизводя себя в ней entirity вместе с соответствующими мутациями. Физическая последовательность ДНК является "неизменной".

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

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

(+ 1 2 3)

который будет делать то же самое, что

(eval '(+ 1 2 3))

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

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

'(+ 1 2 3)

к

'(+ 1 4 3)

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

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

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

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

  • Архитектура Non-Von-Neumann.Если кто-то может изобрести какую-то удивительную гипотетическую машину, где представление программ самого низкого уровня - это АСТ, который может быть выполнен непосредственно (дальнейшая компиляция не нужна). На такой машине AST будет представлять как исполняемые инструкции, так и данные. К сожалению, проблема не будет решена, потому что AST по-прежнему должен быть либо кодом, либо данными. Присутствие функции eval не меняет этого. В машинный код вы можете переключаться между кодом и данными столько, сколько хотите. Принимая во внимание, что с eval и Lisp после того, как вы "обнулили" некоторый список из данных в код и выполнили его, нет способа вернуть этот список обратно. Фактически, этот список ушел навсегда и был заменен его значением. Нам не хватало бы чего-то важного, что обычно бывает указателем.
  • Список ярлыков. Если требовалось, чтобы в каждом списке также была уникальная метка, можно было бы сделать косвенную самостоятельную модификацию, запустив функции против списка с указанной меткой. В сочетании с продолжениями это, наконец, позволит самомодифицировать код в том же смысле, что и машинный код. Этикетки эквивалентны адресам памяти машинного кода. В качестве примера рассмотрим программу Lisp, где верхний node AST имеет метку "main". Внутри main вы могли бы затем выполнить функцию, которая берет метку, Integer, Atom и копирует атом в список с меткой, которая соответствует той, которая указана в функции, в индексе, указанном Integer. Затем просто позвоните с текущим продолжением на главном. Там вы идете, самостоятельно изменяя код.
  • Lisp Макросы. Я не нашел времени, чтобы понять макросы Lisp, и они могут действительно делать именно то, о чем я думаю.

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

Вопросы

  • Существуют ли какие-либо языки, кроме машинного кода, dna и malbolge, которые могут делать полную модификацию самостоятельно и являются гомомиковидными?
  • (не беспокойтесь, если вы указали tl; dr в приведенном выше тексте). Действительно ли Lisp homoiconic + самомодификация? Если вы так говорите, можете ли вы точно указать, где в моем аргументе я сбился с пути?

Приложение

Языки с неограниченной самостоятельной модификацией, но без гомиконичности

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

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

4b9b3361

Ответ 1

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

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

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

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

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

[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

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

Ответ 2

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

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

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

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

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

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

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

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

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

В Lisp существуют другие подходы к обеспечению доступа к Lisp, на котором запущена ваша программа и программа:

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

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

Ответ 3

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

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

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

Ответ 4

Здесь я встречаюсь как фанат, и я уже говорил об этом раньше, но прочитал Paul Graham В Lisp если вы хотите узнать о макросах Lisp. Они очень важны с точки зрения разрешительных модификаций, которые в противном случае были бы неосуществимыми. Кроме того, я считаю важным различать здесь Lisp семейство языков, заданную Lisp и заданную реализацию Lisp.

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