В чем «хитрость» написания Куайна? - PullRequest
22 голосов
/ 16 января 2012

Я читаю классическую статью Кена Томпсона Размышления о доверии к доверию , в которой он предлагает пользователям написать Куайн в качестве вступления к его аргументу (настоятельно рекомендуется прочитать).

Quine - это компьютерная программа, не требующая ввода и создающая копию собственного исходного кода в качестве единственного вывода.

Наивный подход - просто хотеть сказать:

print "[insert this program's source here]"

Но быстро видно, что это невозможно. В итоге я написал один , используя Python, но все еще не могу объяснить "хитрость". Я ищу отличное объяснение того, почему Куайны возможны.

Ответы [ 2 ]

13 голосов
/ 16 января 2012

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

Стандартный Cпример из http://www.nyx.net/~gthompso/quine.htm очень хорошо иллюстрирует это:

char*f="char*f=%c%s%c;main(){printf(f,34,f,34,10);}%c";main(){printf(f,34,f,34,10);}

edit : После написания этого я немного поискал: http://www.madore.org/~david/computers/quine.html дает оченьхорошее, более теоретическое описание того, что именно представляют собой кваиры и почему они работают.

3 голосов
/ 18 октября 2012

Вот тот, который я написал, который использует putchar вместо printf; таким образом, он должен обрабатывать все свои собственные escape-коды. Но он переносим на 100% во всех наборах символов исполнения C.

Вы должны увидеть, что в текстовом представлении есть шов , который отражает шов в самом тексте программы, где он меняется с работы в начале на работу в конце. Хитрость при написании Quine заключается в преодолении этого «горба», когда вы переключаетесь на копание своего пути из дыры! Ваши параметры ограничены текстовым представлением и выводом языка объекты.

#include <stdio.h>

void with(char *s) {
    for (; *s; s++) switch (*s) {
    case '\n': putchar('\\'); putchar('n'); break;
    case '\\': putchar('\\'); putchar('\\'); break;
    case '\"': putchar('\\'); putchar('\"'); break;
    default: putchar(*s);
    }
}
void out(char *s) { for (; *s; s++) putchar(*s); }
int main() {
    char *a[] = {
"#include <stdio.h>\n\n",
"void with(char *s) {\n",
"    for (; *s; s++) switch (*s) {\n",
"   case '\\",
"n': putchar('\\\\'); putchar('n'); break;\n",
"   case '\\\\': putchar('\\\\'); putchar('\\\\'); break;\n",
"   case '\\\"': putchar('\\\\'); putchar('\\\"'); break;\n",
"   default: putchar(*s);\n",
"    }\n}\n",
"void out(char *s) { for (; *s; s++) putchar(*s); }\n",
"int main() {\n",
"    char *a[] = {\n",
NULL }, *b[] = {
"NULL }, **p;\n",
"    for (p = a; *p; p++) out(*p);\n",
"    for (p = a; *p; p++) {\n",
"   putchar('\\\"');\n",
"   with(*p);\n",
"   putchar('\\\"'); putchar(','); putchar('\\",
"n');\n",
"    }\n",
"    out(\"NULL }, *b[] = {\\",
"n\");\n",
"    for (p = b; *p; p++) {\n",
"   putchar('\\\"');\n",
"   with(*p);\n",
"   putchar('\\\"'); putchar(','); putchar('\\",
"n');\n",
"    }\n",
"    for (p = b; *p; p++) out(*p);\n",
"    return 0;\n",
"}\n",
NULL }, **p;
    for (p = a; *p; p++) out(*p);
    for (p = a; *p; p++) {
    putchar('\"');
    with(*p);
    putchar('\"'); putchar(','); putchar('\n');
    }
    out("NULL }, *b[] = {\n");
    for (p = b; *p; p++) {
    putchar('\"');
    with(*p);
    putchar('\"'); putchar(','); putchar('\n');
    }
    for (p = b; *p; p++) out(*p);
    return 0;
}

Обычный трюк заключается в том, чтобы запустить квинну, написав программу для чтения текстового файла и вывода массива чисел. Затем вы модифицируете его для использования статического массива и запускаете первую программу для новой программы (статический массив), создавая массив чисел, который представляет программу. Вставьте это в статический массив, запустите его снова, пока он не успокоится, и вы получите квиню. Но , он привязан к определенному набору символов (== не на 100% переносимо). Подобная выше программа (а не классический printf hack) будет работать одинаково на ASCII или EBCDIC (классический printf hack не выполняется в EBCDIC, поскольку содержит жестко закодированный ASCII).


редактирование:

Читая вопрос снова, внимательно (наконец), кажется, что вы на самом деле ищете больше философии, а не техники. Хитрость, которая позволяет вам выйти из бесконечного регресса, - это двухполосный . Вы должны получить и закодированную программу, и расширенную программу из одних и тех же данных: используя одни и те же данные двумя способами. Таким образом, эти данные описывают только часть программы, окружающую ее будущее проявление, frame . Изображение в этом кадре является прямой копией оригинала.

Вот так вы бы естественным образом создавали рекурсивный рисунок вручную: телевизор из телевизора. В какой-то момент вы устаете и просто рисуете блики на экране, потому что рекурсия уже достаточно установлена.


редактирование:

Я ищу отличное объяснение того, почему Куайны возможны.

«Возможность» Куайна уходит в глубины математических революций 19 и 20 веков. «Классическая» куина В. В. Куайна - это последовательность слов (IIRC)

возвращает ложь при добавлении к себе

, что является парадоксом, сродни просьбе Давида о чем-то, что «делает меня счастливым, когда грустно, и делает меня грустным, когда счастлив», на что отвечает медальон с надписью с обеих сторон: «это тоже пройдет».

Тот же самый узел был исследован пионерами современной математической логики, такими как Фреге, Рассел и Уайтхед, Лукасевич, и, конечно, наши ребята Тьюринг, Черч и Тью. Уловка, которая позволяет транспонировать Quine из области игры слов в программную демонстрацию (раскручивая часть paradox по пути), была методом Гёделя для кодирования самих арифметических операций. что касается чисел, то все математическое выражение может быть закодировано в одно (огромное) целое число. В частности, математическая функция, которая выполняет декодирование этого представления, может быть выражена в той же (числовой) форме. Это число (функция, закодированная по Гёделю) равно и коду, и к данным.

Это степенное трио (код, представление, данные) может быть перенесено в различные представления. Выбирая другое представление (или цепочку , например: bytes-> ASCII-> hexadecimal-> integer), изменяет поведение кода, которое изменяет внешний вид данных.

...