Гомоиконный и «неограниченный» самомодифицирующийся код + Действительно ли lisp самомодифицируется?

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

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

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

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

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

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

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

(+ 1 2 3)

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

(eval '(+ 1 2 3))

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

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

'(+ 1 2 3)

на

'(+ 1 4 3)

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

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

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

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

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

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

Вопросы

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

Приложение

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

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

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

29
задан TheIronKnuckle 13 December 2011 в 14:12
поделиться