맨위로가기

콰인 (컴퓨팅)

"오늘의AI위키"는 AI 기술로 일관성 있고 체계적인 최신 지식을 제공하는 혁신 플랫폼입니다.
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.

1. 개요

콰인은 자기 자신을 출력하는 컴퓨터 프로그램이다. 1940년대 존 폰 노이만이 '스스로를 재생산하는 오토마타' 개념을 이론화한 것에서 유래되었으며, 1972년 폴 브래틀리와 장 밀로가 논문에서 콰인을 논의했다. 콰인은 일반적으로 소스 코드와 데이터 부분으로 구성되며, 코드는 데이터를 사용하여 코드 자체를 출력한다. C, Java, Python, Ruby, Lua, Scheme 등 다양한 프로그래밍 언어로 구현될 수 있으며, 콰인의 변형으로 우로보로스 프로그램, 멀티콰인, 폴리글롯, 방사선 내성 콰인 등이 있다. 또한, 자기 평가형 숫자나 빈 소스 파일을 이용하는 '부정행위' 콰인도 존재한다.

더 읽어볼만한 페이지

  • 컴퓨터 프로그래밍 - 순서도
    순서도는 컴퓨터 알고리즘이나 프로세스를 시각적으로 표현하는 도구로, 흐름 공정 차트에서 기원하여 컴퓨터 프로그래밍 분야에서 알고리즘을 설명하는 데 사용되며, 다양한 종류와 소프트웨어 도구가 존재한다.
  • 컴퓨터 프로그래밍 - 의사코드
    의사코드는 컴퓨터 과학 및 수치 계산 분야에서 알고리즘을 설명하기 위해 사용되는 비표준적인 언어로, 자연어와 프로그래밍 언어의 요소를 혼합하여 알고리즘의 논리적 흐름을 이해하기 쉽게 하고 프로그래머가 실제 코드로 구현하기 전에 알고리즘을 설계하고 검토하는 데 유용하다.
콰인 (컴퓨팅)

2. 역사

자기 복제 프로그램, 즉 콰인의 개념적 뿌리는 1940년대 존 폰 노이만이 자기 복제 오토마타와 universal constructor 개념을 이론화한 것에서 찾을 수 있다.[1]

실제 자기 복제 프로그램의 아이디어는 1960년대 에든버러 대학교의 강사이자 연구원이었던 Hamish Dewar가 작성한 Atlas Autocode 프로그램에서 비롯되었다. 이 프로그램은 다음과 같다.



%BEGIN

!THIS IS A SELF-REPRODUCING PROGRAM

%ROUTINESPEC R

R

PRINT SYMBOL(39)

R

PRINT SYMBOL(39)

NEWLINE

%CAPTION %END~

%CAPTION %ENDOFPROGRAM~

%ROUTINE R

%PRINTTEXT '

%BEGIN

!THIS IS A SELF-REPRODUCING PROGRAM

%ROUTINESPEC R

R

PRINT SYMBOL(39)

R

PRINT SYMBOL(39)

NEWLINE

%CAPTION %END~

%CAPTION %ENDOFPROGRAM~

%ROUTINE R

%PRINTTEXT '

%END

%ENDOFPROGRAM



이후 1972년, 폴 브래틀리(Paul Bratley)와 장 밀로(Jean Millo)는 ''Computer Recreations; Self-Reproducing Automata''라는 논문에서 자기 복제 프로그램에 대해 논의했다.[1] 브래틀리는 Hamish Dewar가 작성한 위의 프로그램을 보고 이러한 유형의 프로그램에 관심을 가지게 되었다고 밝혔다.

"콰인"이라는 용어는 더글러스 호프스태터가 그의 유명한 대중 과학 서적 ''괴델, 에셔, 바흐''에서 철학자 윌러드 밴 오먼 콰인(1908–2000)의 이름을 따서 만들었다. 콰인 철학자는 간접 자기 참조 문제를 깊이 연구했으며, 특히 다음과 같은 문장으로 유명한 콰인의 역설을 제시했다.

"자신의 인용구 앞에 붙으면 거짓을 생성한다"는 자신의 인용구 앞에 붙으면 거짓을 생성한다.


클리니 재귀 정리에 따르면, 어떤 계산 가능한 문자열이라도 출력할 수 있는 프로그래밍 언어에는 반드시 콰인이 존재한다는 것이 수학적으로 증명된다.

한편, 콰인의 아이디어는 소프트웨어 라이선스에도 영향을 미쳤는데, GNU 아페로 일반 공중 사용 허가서(AGPL)의 "소스 코드 다운로드" 요구 사항은 이러한 자기 참조적인 개념에 기반하고 있다.[2]

3. 작동 원리

콰인은 자기 자신의 소스 코드를 그대로 출력하는 프로그램을 말한다. 이러한 자기 복제 능력은 1940년대 존 폰 노이만이 구상한 자기 복제 기계의 개념과 연관된다.[1]

일반적으로 콰인은 두 가지 주요 부분으로 구성된다.


  • 코드: 프로그램의 실행 로직을 담고 있으며, 실제로 출력을 수행하는 부분이다.
  • 데이터: 코드 부분 자체를 문자열이나 리스트 등의 형태로 저장하고 있는 부분이다.


콰인의 작동 원리는 코드 부분이 데이터 부분을 이용하여 전체 소스 코드를 재구성하여 출력하는 방식이다. 즉, 코드 부분은 저장된 데이터(코드 자신의 텍스트 표현)를 읽어와 코드 부분을 출력하고, 동시에 데이터 자체를 적절히 가공하여 데이터 부분의 텍스트 표현까지 출력한다.

만약 단순히 프로그램 소스 안에 자기 자신의 코드를 문자열로 포함시키려고 하면, "나는 '나는 ...라고 말했다'라고 말했다"와 같이 문자열 안에 또 문자열을 넣어야 하는 무한 후퇴 문제에 빠지게 된다. 콰인은 코드와 데이터를 분리하고, 코드가 데이터를 활용하여 전체 소스 코드를 생성하는 방식으로 이 문제를 해결한다.

다양한 프로그래밍 언어로 콰인을 만들 수 있으며, 각 언어의 특성을 활용한 여러 구현 방식이 존재한다.

C 언어로 작성된 고전적인 콰인 예시는 다음과 같다. 이 코드는 문자열 데이터(`S`)를 `printf` 함수의 포맷 문자열과 인자로 동시에 사용하여 자기 자신을 출력한다. ASCII 코드 값(10은 개행 문자, 34는 큰따옴표)을 사용하여 탈출 문자 문제를 피하고 있다.

```c

#include

char S[] = "#include %cchar S[] = %c%s%c;%cint main() { printf(S, 10, 34, S, 34, 10); return 0; }";

int main() { printf(S, 10, 34, S, 34, 10); return 0; }

```

자바에서는 문자열 배열을 사용하여 소스 코드의 각 줄을 데이터로 저장하고, 반복문을 통해 이 배열 데이터를 조합하여 전체 코드를 출력하는 방식으로 콰인을 구현할 수 있다.

```java

public class Quine

{

public static void main(String[] args)

{

char q = 34; // 따옴표 문자

String[] l = { // 소스 코드 배열

"public class Quine",

"{",

" public static void main(String[] args)",

" {",

" char q = 34; // 따옴표 문자",

" String[] l = { // 소스 코드 배열",

" ",

" };",

" for (int i = 0; i < 6; i++) // 시작 코드 인쇄",

" System.out.println(l[i]);",

" for (int i = 0; i < l.length; i++) // 문자열 배열 인쇄 (데이터 부분)",

" System.out.println(l[6] + q + l[i] + q + ',');",

" for (int i = 7; i < l.length; i++) // 나머지 코드 인쇄",

" System.out.println(l[i]);",

" }",

"}",

};

for (int i = 0; i < 6; i++) // 시작 코드 인쇄

System.out.println(l[i]);

for (int i = 0; i < l.length; i++) // 문자열 배열 인쇄 (데이터 부분)

System.out.println(l[6] + q + l[i] + q + ','); // l[6]은 들여쓰기용 공백, q는 따옴표

for (int i = 7; i < l.length; i++) // 나머지 코드 인쇄

System.out.println(l[i]);

}

}

```

이 자바 코드는 소스 코드 자체를 문자열 배열 `l`에 저장하고, 반복문을 사용하여 배열의 각 요소를 출력하되, 배열 자체를 출력하는 부분에서는 각 요소 앞뒤에 따옴표(`q`)와 쉼표를 붙여 출력함으로써 원본 소스 코드의 데이터 부분을 재현한다.[3]

파이썬, 루비, Lua와 같이 문자열을 코드로 실행(evaluation)하는 기능을 가진 언어에서는 이를 활용하여 더 간결한 콰인을 만들기도 한다. 예를 들어 파이썬에서는 다음과 같이 작성할 수 있다.

```python

# %r은 문자열을 따옴표로 감싸서 표현해준다.

c = 'c = %r; print(c %% c)'; print(c % c)

```

한편, GNU 아페로 일반 공중 사용 허가서에서 네트워크를 통해 소프트웨어를 사용하는 사용자에게도 소스 코드를 제공하도록 요구하는 조항은, 프로그램이 자신의 소스 코드에 접근하고 이를 제공한다는 점에서 콰인의 개념과 유사한 측면을 가진다.[2]

4. 예제

콰인 프로그램, 즉 자기 자신의 소스 코드를 출력하는 프로그램의 아이디어는 폴 브래틀리(Paul Bratley)와 장 밀로(Jean Millo)의 ''Computer Recreations; Self-Reproducing Automata''에서 처음 등장했다. 브래틀리는 1960년대에 아틀라스 오토코드로 작성된 다음 프로그램을 보고 이러한 프로그램에 관심을 가지게 되었다고 한다.



%BEGIN

!THIS IS A SELF-REPRODUCING PROGRAM

%ROUTINESPEC R

R

PRINT SYMBOL(39)

R

PRINT SYMBOL(39)

NEWLINE

%CAPTION %END~

%CAPTION %ENDOFPROGRAM~

%ROUTINE R

%PRINTTEXT '

%BEGIN

!THIS IS A SELF-REPRODUCING PROGRAM

%ROUTINESPEC R

R

PRINT SYMBOL(39)

R

PRINT SYMBOL(39)

NEWLINE

%CAPTION %END~

%CAPTION %ENDOFPROGRAM~

%ROUTINE R

%PRINTTEXT '

%END

%ENDOFPROGRAM



일반적으로 대부분의 프로그래밍 언어에서 콰인을 만드는 방법은 프로그램 내부에 다음 두 부분을 구성하는 것이다.


  • (a) 실제 출력을 수행하는 데 사용되는 소스 코드 부분
  • (b) 코드 자체의 텍스트 형식을 나타내는 데이터 부분 (주로 문자열 형태)


코드는 데이터를 사용하여 자기 자신의 소스 코드를 출력한다. 데이터는 코드의 텍스트 형식을 나타내므로, 이 데이터를 이용해 코드를 출력하는 것은 당연하다. 또한, 데이터는 간단한 방식으로 처리되어 데이터 자체의 텍스트 표현을 출력하는 데에도 사용된다. 즉, 프로그램은 '코드 부분'과 '데이터 부분'을 모두 출력해야 하며, '데이터 부분'은 '코드 부분'과 '데이터 부분' 전체를 문자열 형태로 가지고 있어야 한다.

다음은 MS-Office VBA (매크로)로 작성된 콰인의 예시이다. 이 코드는 직접 실행 창에 입력하여 실행할 수 있다.



:i="&chr(34):?mid(i,34);i;mid(i,1,34):i="&chr(34):?mid(i,34);i;mid(i,1,34):



다양한 프로그래밍 언어로 콰인을 작성할 수 있으며, C, Java, 파이썬, Ruby, Lua, Scheme, Haskell, Brainfuck, HQ9+ 등의 구체적인 예시는 아래 하위 섹션에서 확인할 수 있다.

4. 1. C (프로그래밍 언어)

다음은 C로 작성된 전통적인 콰인의 예시이다. 이 프로그램은 ASCII 문자 집합을 사용하는 환경에서 정상적으로 작동한다.



#include

char S[] = "#include %cchar S[] = %c%s%c;%cint main() { printf(S, 10, 34, S, 34, 10); return 0; }";

int main() { printf(S, 10, 34, S, 34, 10); return 0; }



이 코드는 printf 함수의 포맷 문자열과 포맷 인자에 동일한 문자열 `S`를 사용하여 `S`의 내용을 출력하는 방식을 사용한다. C 언어에서 탈출 문자로 사용되는 역슬래시(\)나 큰따옴표(") 등을 문자열 안에 직접 포함시키기 어려우므로, 대신 해당 문자의 ASCII 코드 값(줄 바꿈 문자 10, 큰따옴표 34)을 직접 사용하여 이를 회피한다.

다음은 가독성을 고려하여 작성된 또 다른 C 언어 콰인 예시이다. 코드를 문자열(`progdata`)로 저장하고, 코드 부분과 문자열 자체를 출력하는 방식을 사용한다.



/* 표준 C로 작성된 간단한 콰인(자체 인쇄 프로그램)입니다. */

/* 참고: 이 콰인을 설계할 때 코드를 간결하고 난해하게 만들기보다는 코드를 명확하고 읽기 쉽게 만드는 데 중점을 두었습니다.

  • 따라서 일반적인 원리를 길이의 희생을 감수하면서 명확하게 만들 수 있습니다.
  • 간단히 말해, 아래의 "progdata"라고 불리는 동일한 데이터 구조를 사용하여 프로그램 코드(해당 코드를 나타냄)와
  • 자체 텍스트 표현을 출력합니다. */


#include

void quote(const char *s)

/* 이 함수는 문자열 s를 받아서 C 코드에서 형식화된 것처럼 보일 수 있는 s의 텍스트 표현을 인쇄합니다. */

{

int i;

printf(" \"");

for (i = 0; s[i]; ++i) {

/* 특정 문자는 인용됩니다. */

if (s[i] == '\\')

printf("\\\\");

else if (s[i] == '"')

printf("\\\"");

else if (s[i] == '\n')

printf("\\n");

/* 다른 문자는 그대로 인쇄됩니다. */

else

printf("%c", s[i]);

/* 가끔 줄 바꿈을 삽입합니다. */

if (i % 48 == 47)

printf("\"\n \"");

}

printf("\"");

}

/* 다음에 나오는 내용은 프로그램 코드의 문자열 표현이며,

  • 처음부터 끝까지 (quote() 함수에 따라 형식화됨)
  • 단, 문자열 _자체_는 두 개의 '@' 문자로 코딩됩니다. */

const char progdata[] =

"/* 표준 C로 작성된 간단한 콰인(자체 인쇄 프로그램)입니다. */\n\n/* 참고: 이 콰인을 설계할 때 "

"코드를 명확하고 읽기 쉽게 만들려고 시도했으며, 콰인이 많은 만큼 간결하고 난해하지는 않습니다."

", 따라서\n * 일반적인 원리를 길이의 희생을 감수하면서 명확하게 만들 수 있습니다.\n * 간단히 말해: "

"동일한 데이터 구조(아래의 \"progdata\"라고 함)\n"

" * 를 사용하여 프로그램 코드(해당 코드를 나타냄)와 자체 텍스트 표현을 출력합니다.\n\n#include \n\nvoid quote(const char "

"*s)\n /* 이 함수는 문자열 s를 받아서 C 코드에서 형식화된 것처럼 보일 수 있는 s의\n * 텍스트 표현을 인쇄합니다. */\n{\n int i;\n\n printf(\" \\\"\");\n "

" for (i=0; s[i]; ++i) {\n /* 특정 문자들은 인용됩니다. */\n if (s[i] == '\\\\')"

"\n printf(\"\\\\\\\\\");\n else if (s["

"i] == '\"')\n printf(\"\\\\\\\"\");\n e"

"lse if (s[i] == '\\n')\n printf(\"\\\\n\");"

"\n /* 다른 문자는 그냥 인쇄됩니다. */\n"

" else\n printf(\"%c\", s[i]);\n "

" /* 가끔 줄 바꿈을 삽입합니다. */\n "

" if (i % 48 == 47)\n printf(\"\\\"\\"

"n \\\"\");\n }\n printf(\"\\\"\");\n}\n\n/* 다음에 나오는 것은 프로그램 코드의 문자열 표현입니다. "

"code,\n * 처음부터 끝까지 (quote() 함수에 따라 형식화됨)\n * 단, 문자열 _자체_는 두 개의 \n * 연속적인 '@' 문자로 코딩됩니다. */\nconst char progdata[] =\n@@;\n\n"

"int main(void)\n /* 프로그램 자체... */\n"

"{\n int i;\n\n /* 프로그램 코드를 문자별로 인쇄합니다. */\n for (i=0; progdata[i"

"]; ++i) {\n if (progdata[i] == '@' && prog"

"data[i+1] == '@')\n /* 두 개의 '@' 기호를 만나면 인용된\n "

" * 프로그램 코드의 형식을 인쇄해야 합니다. */\n {\n "

" quote(progdata); /* 모두 인용합니다. */\n"

" i++; /* 두 번째 '@'를 건너뜁니다. */\n"

" } else\n printf(\"%c\", p"

"rogdata[i]); /* 문자를 인쇄합니다. */\n }\n r"

"eturn 0;\n}\n";

int main(void)

/* 프로그램 자체... */

{

int i;

/* 프로그램 코드를 문자별로 인쇄합니다. */

for (i = 0; progdata[i]; ++i) {

if (progdata[i] == '@' && progdata[i + 1] == '@')

/* 두 개의 '@' 기호를 만나면 인용된

  • 프로그램 코드의 형식을 인쇄해야 합니다. */

{

quote(progdata); /* 모두 인용합니다. */

i++; /* 두 번째 '@'를 건너뜁니다. */

} else

printf("%c", progdata[i]); /* 문자를 인쇄합니다. */

}

return 0;

}



이 콰인은 `progdata` 문자열 안에 프로그램 전체 코드를 담고 있다. 단, 문자열 자체를 표현해야 하는 부분은 `@@` 기호로 표시해 두었다. 프로그램 실행 시 `progdata`를 한 글자씩 출력하다가 `@@`를 만나면 `quote` 함수를 호출하여 `progdata` 문자열 전체를 C 언어 문자열 형식으로 출력한다. `quote` 함수는 문자열 내의 특수 문자(\, ", \n 등)를 적절히 이스케이프 처리하여 출력한다.

전처리기를 사용한 콰인 예시도 있다.



#include

int main(int argc, char** argv)

{

#define B(x) x; printf(" B(" #x ")\n");

#define A(x) printf(" A(" #x ")\n"); x;

B(printf("#include \nint main(int argc, char** argv)\n{\n#define B(x) x; printf(\" B(\" #x \")\\n\");\n#define A(x) printf(\" A(\" #x \")\\n\"); x;\n"))

A(printf("}\n"))

}



이 코드는 매크로 `A`와 `B`를 정의하여 사용한다. 매크로 내부에서는 `#` 연산자를 사용하여 매크로 인자를 문자열로 만들고, 이를 printf 함수로 출력하여 소스 코드 자체를 생성한다.

전처리기를 사용하지 않고 printf 함수만을 이용하여 더 짧은 콰인을 만들 수도 있다. 다음 예시는 서식 문자열과 인자를 주의 깊게 배치하여 이를 구현한다. 여기서 숫자 34는 큰따옴표(")의 ASCII 코드로, 문자열 리터럴 내에서 큰따옴표를 직접 사용하지 않고 출력하기 위해 사용되었다.



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



이 코드는 문자열 `s`에 프로그램의 기본 구조를 담고, printf 함수를 이용해 이 문자열을 출력한다. 이때 `%c` 서식 지정자에는 ASCII 코드 34(큰따옴표)를, `%s` 서식 지정자에는 문자열 `s` 자체를 전달하여 결과적으로 소스 코드와 동일한 출력을 만들어낸다.

4. 2. Java (프로그래밍 언어)

다음은 자바로 작성된 콰인 프로그램의 한 예시이다.

```java

public class Quine

{

public static void main(String[] args)

{

char q = 34; // 따옴표 문자

String[] l = { // 소스 코드 배열

"public class Quine",

"{",

" public static void main(String[] args)",

" {",

" char q = 34; // 따옴표 문자",

" String[] l = { // 소스 코드 배열",

" ",

" };",

" for (int i = 0; i < 6; i++) // 시작 코드 인쇄",

" System.out.println(l[i]);",

" for (int i = 0; i < l.length; i++) // 문자열 배열 인쇄",

" System.out.println(l[6] + q + l[i] + q + ',');",

" for (int i = 7; i < l.length; i++) // 이 코드 인쇄",

" System.out.println(l[i]);",

" }",

"}",

};

for (int i = 0; i < 6; i++) // 시작 코드 인쇄

System.out.println(l[i]);

for (int i = 0; i < l.length; i++) // 문자열 배열 인쇄

System.out.println(l[6] + q + l[i] + q + ',');

for (int i = 7; i < l.length; i++) // 이 코드 인쇄

System.out.println(l[i]);

}

}

```

일반적으로 모든 프로그래밍 언어에서 콰인을 만드는 방법은 프로그램 내에 다음 두 부분을 포함하는 것이다.

  • (a) 실제 출력을 수행하는 소스 코드 부분
  • (b) 소스 코드 자체의 텍스트 표현을 담고 있는 데이터 부분


코드는 데이터를 사용하여 코드 자체를 출력한다. 데이터는 코드의 텍스트 형식을 나타내므로 이는 당연한 과정이다. 또한, 데이터는 간단한 처리를 거쳐 데이터 자체의 텍스트 표현을 출력하는 데에도 사용된다.

위 자바 코드 예시에서는 `String[] l` 배열이 (b) 데이터 부분에 해당하며, 나머지 코드가 (a) 소스 코드 부분이다. 코드는 먼저 `l` 배열의 앞부분(인덱스 0~5)을 출력하여 프로그램의 시작 부분을 구성한다. 그다음, `l` 배열의 모든 요소를 순회하며 각 요소를 따옴표(`q` 변수, ASCII 코드 34)로 감싸고 쉼표를 붙여 출력한다. 이 과정은 `l` 배열 자체를 초기화하는 코드를 생성한다. 마지막으로 `l` 배열의 뒷부분(인덱스 7부터 끝까지)을 출력하여 프로그램의 나머지 부분을 완성한다. 이를 통해 소스 코드 전체가 출력되는 것이다.

이 코드는 c2.com의 원본 게시물을 수정하여 작성되었으며, 작성자 제이슨 윌슨(Jason Wilson)은 Java 주석 없이 콰인의 최소 버전으로 게시했다.[3]

Java 15 버전부터 도입된 텍스트 블록 기능을 사용하면 더 읽기 쉽고 간결한 콰인 작성이 가능하다.[4]

```java

public class Quine {

public static void main(String[] args) {

String textBlockQuotes = new String(new char[]{'"', '"', '"'});

char newLine = 10;

String source = """

public class Quine {

public static void main(String[] args) {

String textBlockQuotes = new String(new char[]{'"', '"', '"'});

char newLine = 10;

String source = %s;

System.out.print(source.formatted(textBlockQuotes + newLine + source + textBlockQuotes));

}

}

""";

System.out.print(source.formatted(textBlockQuotes + newLine + source + textBlockQuotes));

}

}

```

이 코드는 텍스트 블록(`""" ... """`)을 사용하여 소스 코드 자체를 문자열 데이터(`source`)로 저장한다. `String.formatted()` 메소드를 이용하여 `source` 문자열 내의 `%s` 부분을, 텍스트 블록 문법으로 감싸진 `source` 문자열 자체로 치환하여 출력한다. 여기서 `textBlockQuotes`는 텍스트 블록을 여닫는 세 개의 따옴표 문자열이고, `newLine`은 줄 바꿈 문자(ASCII 코드 10)이다.

4. 3. Python (프로그래밍 언어)

일반적으로 프로그래밍 언어에서 콰인을 만드는 방법은 프로그램 내에 다음 두 부분을 구성하는 것이다.

  • 실제 인쇄를 수행하는 데 사용되는 소스 코드
  • 코드의 텍스트 형식을 나타내는 데이터 (보통 문자열)


코드는 데이터를 사용하여 자신의 소스 코드를 인쇄한다. 이때 데이터는 코드 자체의 텍스트 형식을 나타내므로, 코드를 출력하는 것은 당연해 보인다. 또한, 데이터는 간단한 처리를 거쳐 데이터 자체의 텍스트 표현을 인쇄하는 데에도 사용된다.

다음은 파이썬 3 버전으로 작성된 세 가지 간단한 콰인 예시이다.

예제 A: `chr(39)`는 작은따옴표(`'`) 문자를 반환한다. `format` 메서드를 사용한다.



# 예제 A. chr(39) == "'".

a = 'a = {}{}{}; print(a.format(chr(39), a, chr(39)))'; print(a.format(chr(39), a, chr(39)))



예제 B: 예제 A와 유사하지만, `%` 연산자를 사용한 구식 문자열 포매팅을 사용한다.



# 예제 B. chr(39) == "'".

b = 'b = %s%s%s; print(b %% (chr(39), b, chr(39)))'; print(b % (chr(39), b, chr(39)))



예제 C: `%r` 포매팅은 `repr()` 함수가 반환하는 객체의 공식적인 문자열 표현을 사용하므로, 문자열 주위에 자동으로 따옴표를 추가해준다.



# 예제 C. %r은 자동으로 따옴표를 붙입니다.

c = 'c = %r; print(c %% c)'; print(c % c)



파이썬과 같이 문자열을 프로그램 코드로 평가(실행)하는 기능을 가진 언어에서는 이 기능을 활용하여 더 짧은 콰인을 만들 수 있다. 예를 들어, 파이썬 3.8 버전 이상에서는 할당 표현식 (`:=`, 일명 바다코끼리 연산자)과 `exec` 함수를 사용하여 다음과 같이 작성할 수 있다.



exec(s:='print("exec(s:=%r)"%s)')



또한, 객체의 공식적인 문자열 표현을 반환하는 `repr` 함수를 활용하면 비교적 간단하게 콰인을 만들 수 있다.



_="print(f'_={repr(_)};exec(_)')";exec(_)



이 코드는 변수 `_`에 문자열을 할당하고, 그 문자열 안에 `repr(_)`를 사용하여 변수 `_`의 내용을 포함시킨 뒤, `exec` 함수로 전체 문자열을 실행한다. f-string 포매팅을 사용하여 코드를 구성한다.

4. 4. Ruby (프로그래밍 언어)

일부 프로그래밍 언어는 문자열을 프로그램으로 평가하는 기능을 가지고 있으며, 콰인은 이러한 기능을 활용할 수 있다. 예를 들어, 다음은 루비 콰인이다.



eval s="print 'eval s=';p s"


4. 5. Lua (프로그래밍 언어)

Lua는 다음과 같이 할 수 있다.



s="print(string.format('s=%c%s%c; load(s)()',34,s,34))"; load(s)()


4. 6. Scheme (프로그래밍 언어)

Scheme에서의 콰인 예시는 다음과 같다. 이 코드는 Common Lisp에서도 유효하다. 이 콰인은 프로그램 자체를 입력으로 받아, 데이터 구조를 소스 코드로 변환하는 방식으로 작동한다.

```scheme

((lambda (x) (list x (list 'quote x)))

'(lambda (x) (list x (list 'quote x))))

4. 7. 기타 언어

Haskell에서의 예시이다. Haskell에는 값을 해당 표현으로 변환하는 `show` 함수가 갖춰져 있기 때문에 쉽게 구현할 수 있다.



main = putStrLn $ q ++ show q where q = "main = putStrLn $ q ++ show q where q = "



Brainfuck에서의 예시이다.[21] 개행을 제외하면 404바이트밖에 되지 않는다.




>++>+++>+>+>+++>>>>>>>>>>>>>>>>>>>>>>+>+>++>+++>++>>+++

>+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+>+>>+++>>>>+++>>>+++

>+>>>>>>>++>+++>+++>+>>+++>+++>+>+++>+>+++>+>++>+++>>>+

>+>+>+>++>+++>+>+>>+++>>>>>>>+>+>>>+>+>++>+++>+++>+>>+++

>+++>+>+++>+>++>+++>++>>+>+>++>+++>+>+>>+++>>>+++>+>>>++

>+++>+++>+>>+++>>>+++>+>+++>+>>+++>>+++>>

+

>>>[>]+++>+

[+[<++++++++++++++++>-]<++++++++++.<]



HQ9+에서의 예시이다. HQ9+ 자체에 콰인 프로그램이 내장되어 있기 때문에, 한 글자로 콰인을 구현하는 것이 가능하다.



Q


5. 콰인의 변형

콰인은 자신의 소스 코드를 출력하는 프로그램으로, 컴퓨터 과학의 재귀 개념과 관련이 깊다. 기본적인 콰인 외에도 다양한 형태의 변형된 콰인들이 존재한다.


  • 우로보로스 프로그램: 여러 프로그래밍 언어로 작성된 프로그램들이 순환적으로 다음 언어의 소스 코드를 출력하는 형태이다. 예를 들어, 자바 프로그램이 C++ 코드를 출력하고, 그 C++ 프로그램은 다시 원래 자바 코드를 출력하는 방식이다.
  • 멀티콰인: 여러 언어로 구성된 프로그램 묶음으로, 각 프로그램은 주어진 명령줄 인수에 따라 자기 자신 또는 다른 프로그램의 소스 코드를 출력할 수 있다.
  • 폴리글롯: 여러 프로그래밍 언어의 유효한 코드로 동시에 작성된 프로그램이다. 폴리글롯 자체가 콰인일 필요는 없지만, 특정 실행 방식에서 콰인처럼 동작할 수도 있다.
  • 방사선 내성 콰인: 프로그램 코드의 일부 문자가 제거되더라도 여전히 원래의 소스 코드를 출력할 수 있도록 설계된 콰인이다.

5. 1. 우로보로스 프로그램 (Quine Relay)

우로보로스 프로그램 또는 콰인 릴레이(Quine Relay)는 콰인 개념을 여러 수준의 재귀로 확장한 것이다. 이는 멀티콰인과는 구별된다. 우로보로스 프로그램은 여러 프로그래밍 언어로 구성되며, 각 언어의 소스 코드는 다음 언어의 소스 코드를 출력하고, 이 과정이 순환적으로 반복되어 결국 처음 언어의 소스 코드를 출력하게 된다.

예를 들어, 아래는 자바로 작성된 프로그램으로, 실행하면 C++ 프로그램의 소스 코드를 출력한다. 그리고 이 C++ 프로그램을 컴파일하고 실행하면 다시 원래의 자바 프로그램 소스 코드를 출력하여 주기가 2인 우로보로스 프로그램을 형성한다.
자바 소스 코드 (C++ 코드 출력):

public class Quine

{

public static void main(String[] args)

{

char q = 34;

String[] l = {

" ",

"=============<<<<<<<< C++ Code >>>>>>>>=============",

"#include ",

"#include ",

"using namespace std;",

"",

"int main(int argc, char* argv[])",

"{",

" char q = 34;",

" string l[] = {",

" };",

" for(int i = 20; i <= 25; i++)",

" cout << l[i] << endl;",

" for(int i = 0; i <= 34; i++)",

" cout << l[0] + q + l[i] + q + ',' << endl;",

" for(int i = 26; i <= 34; i++)",

" cout << l[i] << endl;",

" return 0;",

"}",

"=============<<<<<<<< Java Code >>>>>>>>=============",

"public class Quine",

"{",

" public static void main(String[] args)",

" {",

" char q = 34;",

" String[] l = {",

" };",

" for(int i = 2; i <= 9; i++)",

" System.out.println(l[i]);",

" for(int i = 0; i < l.length; i++)",

" System.out.println(l[0] + q + l[i] + q + ',');",

" for(int i = 10; i <= 18; i++)",

" System.out.println(l[i]);",

" }",

"}",

};

for(int i = 2; i <= 9; i++)

System.out.println(l[i]);

for(int i = 0; i < l.length; i++)

System.out.println(l[0] + q + l[i] + q + ',');

for(int i = 10; i <= 18; i++)

System.out.println(l[i]);

}

}


C++ 소스 코드 (자바 코드 출력):

#include

#include

using namespace std;

int main(int argc, char* argv[])

{

char q = 34;

string l[] = {

" ",

"=============<<<<<<<< C++ Code >>>>>>>>=============",

"#include ",

"#include ",

"using namespace std;",

"",

"int main(int argc, char* argv[])",

"{",

" char q = 34;",

" string l[] = {",

" };",

" for(int i = 20; i <= 25; i++)",

" cout << l[i] << endl;",

" for(int i = 0; i <= 34; i++)",

" cout << l[0] + q + l[i] + q + ',' << endl;",

" for(int i = 26; i <= 34; i++)",

" cout << l[i] << endl;",

" return 0;",

"}",

"=============<<<<<<<< Java Code >>>>>>>>=============",

"public class Quine",

"{",

" public static void main(String[] args)",

" {",

" char q = 34;",

" String[] l = {",

" };",

" for(int i = 2; i <= 9; i++)",

" System.out.println(l[i]);",

" for(int i = 0; i < l.length; i++)",

" System.out.println(l[0] + q + l[i] + q + ',');",

" for(int i = 10; i <= 18; i++)",

" System.out.println(l[i]);",

" }",

"}",

};

for(int i = 20; i <= 25; i++)

cout << l[i] << endl;

for(int i = 0; i <= 34; i++)

cout << l[0] + q + l[i] + q + ',' << endl;

for(int i = 26; i <= 34; i++)

cout << l[i] << endl;

return 0;

}



이러한 우로보로스 프로그램은 다양한 프로그래밍 언어 조합과 순환 주기 길이로 만들어졌다. 몇 가지 예시는 다음과 같다.

  • 하스켈 → 파이썬 → 루비 (주기 3)
  • 파이썬 → 배시 → (주기 3)
  • C → 하스켈 → 파이썬 → (주기 4)
  • 하스켈 → → 파이썬 → 루비 → C → 자바 (주기 6)
  • 루비 → 자바 → C# → 파이썬 (주기 4)
  • CC++ → 루비 → 파이썬 → PHP (주기 6)
  • 루비 → 파이썬 → → 루아 → OCaml → 하스켈 → C → 자바 → 브레인퍽 → 화이트스페이스 → 언람다 (주기 11)
  • 루비 → 스칼라 → 스키마 → 사이랩 → 셸(배시) → S-Lang스몰토크 → 스쿼럴3 → 표준 ML → ... → 렉스 (128개 언어 순환, 이전에는 50개)
  • 웹 애플리케이션 (HTML, 자바스크립트, CSS) → C (주기 2, 웹 애플리케이션 소스 코드를 C 코드가 출력)

5. 2. 멀티콰인 (Multiquine)

언랭다의 개발자인 데이비드 마도어는 멀티콰인을 다음과 같이 설명한다.[16]

> "멀티콰인은 서로 다른 r개의 프로그램(r개의 서로 다른 언어로 작성됨 – 이 조건이 없다면 모두 단일 콰인과 같게 만들 수 있음)으로 구성되며, 각 프로그램은 명령줄 인수에 따라 r개의 프로그램 중 어떤 프로그램이든(자기 자신 포함) 인쇄할 수 있다. (속임수는 허용되지 않음: 명령줄 인수는 너무 길면 안 됨 – 프로그램의 전체 텍스트를 전달하는 것은 속임수로 간주됨)."

예를 들어, 2개의 언어로 구성된 멀티콰인, 즉 바이콰인(Biquine)은 다음과 같이 작동한다.

  • 언어 X로 작성된 프로그램 A는 실행 시 자기 자신의 소스 코드(A)를 출력하는 콰인이다.
  • 프로그램 A에 특정 명령줄 인수를 주면, 언어 Y로 작성된 프로그램 B의 소스 코드를 출력한다.
  • 언어 Y로 작성된 프로그램 B는 실행 시 자기 자신의 소스 코드(B)를 출력하는 콰인이다.
  • 프로그램 B에 특정 명령줄 인수를 주면, 언어 X로 작성된 프로그램 A의 소스 코드를 출력한다.


따라서 바이콰인은 두 개의 프로그램 묶음으로 볼 수 있으며, 각 프로그램은 주어진 명령줄 인수에 따라 자신 또는 다른 프로그램의 소스 코드를 출력할 수 있다.

이론적으로 멀티콰인을 구성하는 언어의 수에는 제한이 없다. 파이썬, Perl, C, NewLISP, F# 5개 언어로 구성된 멀티콰인(펜타콰인, Pentquine)이 존재하며[17], 심지어 25개의 다른 언어로 구성된 멀티콰인도 만들어졌다.[18]

5. 3. 폴리글롯 (Polyglot)

다중 콰인과는 유사하지만 다른 점은, 폴리글롯 프로그램은 여러 프로그래밍 언어 또는 파일 형식의 유효한 형식으로 작성된 컴퓨터 프로그램 또는 스크립트이며, 해당 언어들의 구문을 결합하여 만들어진다. 폴리글롯 프로그램은 자기 복제 기능을 가질 필요는 없지만, 폴리글롯 프로그램이 실행 가능한 방식 중 하나 이상에서 콰인이 될 수도 있다.

콰인 및 다중 콰인과 달리, 폴리글롯 프로그램은 클레이니의 재귀 정리의 결과로 임의의 언어 집합 간에 존재한다는 보장이 없다. 이는 구문 간의 상호 작용에 의존하며, 항상 다른 구문에 포함될 수 있다는 증명 가능한 속성이 아니기 때문이다.

5. 4. 방사선 내성 콰인 (Radiation-hardened Quine)

방사선 내성 콰인(Radiation-hardened Quine)은 프로그램 코드에서 어떤 문자 하나를 제거해도 원래의 프로그램을 그대로 출력할 수 있는 콰인이다. 이러한 콰인은 일반적인 콰인보다 훨씬 더 복잡하며, 다음 루비 예시에서 볼 수 있다.[19]



eval='eval$q=%q(puts %q(10210/#{1 1 if 1==21}}/.i rescue##/

1 1"[13,213].max_by{|s|s.size}#"##").gsub(/\d/){["=\47eval$q=%q(#$q)#\47##\47

",:eval,:instance_,"||=9"][eval$&]}

exit)#'##'

instance_eval='eval$q=%q(puts %q(10210/#{1 1 if 1==21}}/.i rescue##/

1 1"[13,213].max_by{|s|s.size}#"##").gsub(/\d/){["=\47eval$q=%q(#$q)#\47##\47

",:eval,:instance_,"||=9"][eval$&]}

exit)#'##'

/#{eval eval if eval==instance_eval}}/.i rescue##/

eval eval"[eval||=9,instance_eval||=9].max_by

6. 콰인 생성 방법

관계형 프로그래밍 기법을 사용하면 언어의 인터프리터(또는 동등하게 컴파일러와 런타임)를 관계형 프로그램으로 변환한 다음 고정점을 구하여 자동으로 콰인을 생성할 수 있다.[20]

7. "부정행위" 콰인

많은 함수형 프로그래밍 언어(예: Scheme 및 기타 Lisp)와 APL 같은 대화형 언어에서는 숫자가 자기 평가형이다. 즉, 숫자 자체가 코드가 되고 그 실행 결과도 같은 숫자가 된다. TI-BASIC에서는 프로그램의 마지막 줄이 값을 반환하면 그 값이 화면에 표시되므로, 숫자 하나만 있는 프로그램은 그 숫자 자체를 출력하게 된다. 따라서 이런 언어들에서는 숫자 하나로 이루어진 프로그램이 1바이트 콰인이 될 수 있다. 하지만 이런 코드는 스스로를 '구성'하는 과정을 거치지 않기 때문에, 종종 콰인의 규칙을 교묘히 이용한 부정행위로 여겨진다.



1



일부 언어, 특히 Bash, Perl, Python과 같은 스크립트 언어C에서는 내용이 전혀 없는 빈 소스 파일도 오류 없이 실행되는 유효한 프로그램이다. 이런 빈 프로그램은 아무런 출력도 생성하지 않으므로, 입력(빈 파일)과 출력(없음)이 같다고 볼 수 있는 언어의 고정점(fixed point)이 된다. 이러한 빈 프로그램은 "세계에서 가장 작은 자기 재생산 프로그램"이라는 이름으로 국제 난해 C 코드 경연 대회(IOCCC)에 제출되어 "규칙의 최악의 남용" 상을 받기도 했다.[5] 이 프로그램은 실제로 컴파일 과정을 거치지 않고, 유닉스 명령어인 `cp`를 사용해 원본 파일(빈 파일)을 다른 이름의 파일로 복사한 뒤 실행하는 방식으로 작동했으며, 당연히 아무것도 출력하지 않았다.[6]

콰인은 정의상 어떤 형태의 입력도 받아서는 안 된다. 자신의 소스 코드가 담긴 파일을 읽는 행위 역시 입력으로 간주되므로, 이는 명백한 부정행위이다. 예를 들어, 다음 스크립트는 자신의 파일 경로를 나타내는 `$0` 변수를 이용해 `cat` 명령어로 파일 내용을 그대로 출력하지만, 파일을 직접 읽기 때문에 콰인이 아니다.



#!/bin/sh

# 유효하지 않은 콰인.

# 디스크에서 실행된 파일을 읽는 것은 부정행위다.

cat $0



쉬뱅(`#!`) 지시어의 동작 원리를 이용해 더 짧게 만든 변형도 있다. 이 스크립트는 실행될 때 `/bin/cat` 프로그램을 인터프리터로 사용하여 스크립트 파일 자체의 내용을 출력하게 하지만, 역시 파일을 읽는 방식이므로 콰인이 아니다.



#!/bin/cat



또 다른 의심스러운 기술로는 컴파일러나 인터프리터가 출력하는 오류 메시지를 이용하는 방법이 있다. 예를 들어, 오래된 GW-BASIC 환경에서는 `Syntax Error`라는 잘못된 구문을 입력하면, 인터프리터가 똑같이 `Syntax Error`라는 메시지를 출력한다. 입력과 출력이 같아 보이지만, 이는 프로그램 자체의 능력이 아니라 외부 환경(인터프리터)의 반응을 이용한 것이므로 콰인으로 인정받기 어렵다.

참조

[1] 간행물 Computer Recreations: Self-Reproducing Automata
[2] 웹사이트 stet and AGPLv3 http://www.softwaref[...] Software Freedom Law Center 2008-06-14
[3] 웹사이트 Quine Program http://wiki.c2.com/?[...]
[4] 웹사이트 Simple Java quine, self replicating (Self copying) Java code, with text blocks. This code can be run with Java 15+ or Java 13+ with special flags. License is public domain, no rights reserved https://gist.github.[...]
[5] 웹사이트 IOCCC 1994 Worst Abuse of the Rules http://www0.us.ioccc[...]
[6] 웹사이트 Makefile http://www0.us.ioccc[...] 2019-04-04
[7] 웹사이트 A Third Order Quine in Three Languages http://blog.sigfpe.c[...] 2008-02-05
[8] 웹사이트 Ask and ye shall receive: Self-replicating program that goes through three generations, Python, Bash, Perl http://www.stratiger[...] 2011-03-17
[9] 웹사이트 multiquine http://hpaste.org/43[...] 2011-02-01
[10] 웹사이트 Quine Central http://blog.sigfpe.c[...] 2011-01-30
[11] 웹사이트 Quine Ruby -> Java -> C# -> Python http://ruslan.ibragi[...] 2013-04-20
[12] 웹사이트 Quine by shinh (C C++ Ruby Python PHP Perl) http://golf.shinh.or[...] 2007-11-10
[13] 웹사이트 Uroboros Programming With 11 Programming Languages http://asiajin.com/b[...] 2011-03-17
[14] 웹사이트 Quine Relay - An uroboros program with 100+ programming languages https://github.com/m[...] 2021-11-02
[15] 웹사이트 C Prints JavaScript http://michaelwehar.[...] 2019-11-10
[16] 웹사이트 Quines (self-replicating programs) http://www.madore.or[...]
[17] 웹사이트 Pentaquine - 5 part multiquine https://github.com/r[...] 2020-01-14
[18] 웹사이트 Quine Chameleon#Variants https://github.com/c[...] 2021-05-21
[19] 웹사이트 Radiation-hardened Quine https://github.com/m[...] 2014-02-24
[20] 서적 Proceedings of the 2012 Annual Workshop on Scheme and Functional Programming Association for Computing Machinery 2012-09-09
[21] 뉴스 quine.bf - λx.x K S K @ はてな https://keisukenakan[...] はてなダイアリー 2018-07-12



본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.

문의하기 : help@durumis.com