Unknownpgr

콰인(Quine)

2021-04-05 21:27:20 | Korean

소개

인터넷을 돌아다니다가 뜬금없이 Quine이라는 것을 발견했습니다. 콰인이란 어떤 프로그램 소스 코드로, 실행시켰을 때 그 자신을 출력하는 것을 말합니다. 가장 자명한 콰인은 아무것도 없는 소스코드입니다. 아무것도 없는 소스코드는 아무것도 출력하지 않으므로, 자기 자신을 출력한다고 볼 수 있습니다. 당연하지만, eval, exec류의 string 실행 함수, reflection, 그냥 파일 읽기, 외부 라이브러리 등을 사용하면 안 됩니다.

소스코드

저는 아래와 같은 python 코드로 콰인을 작성했습니다.

def decode(s):
    s = s.replace("\\", "\\\\")
    s = s.replace('"', '\\"')
    s = s.replace("'", "\\'")
    s = s.replace('''
''', '\\n')
    return s


def self_print(x):
    print(f'''{x}

self_print(
    "{decode(x)}")''')


self_print(
    "def decode(s):\n    s = s.replace(\"\\\\\", \"\\\\\\\\\")\n    s = s.replace(\'\"\', \'\\\\\"\')\n    s = s.replace(\"\'\", \"\\\\\'\")\n    s = s.replace(\'\'\'\n\'\'\', \'\\\\n\')\n    return s\n\n\ndef self_print(x):\n    print(f\'\'\'{x}\n\nself_print(\n    \"{decode(x)}\")\'\'\')")

위 코드를 실행하면 자기 자신과 완벽히 똑같은 출력을 내보냅니다.

논리적 도출

저는 콰인을 구현하는 방법에 대해 한 번도 들어본 적이 없기 때문에 순수하게 바닥에서부터 출발했고, 따라서 다른 코드들과 방향성이 좀 다를 수 있습니다.

먼저 위 함수는 다음과 같은 아이디어에서 출발했습니다.

만약 언어 자체에 self_print(x) 라는 함수가 있어서, 이 함수의 출력이

self_print("x")

라면 어떨까?

만약 그렇다면 다음과 같은 코드는 콰인이 됩니다.

self_print("아무 문자열")

그러나 당연하게도 파이썬에는 저런 함수가 없습니다. 그러므로 self_print함수를 직접 정의할 필요가 있습니다.

def self_print(x):
	# ~~~

sef_print("아무 문자열")

그런데 여기서 문제가 발생합니다. self_print함수의 정의 자체는 출력하지 않기 때문에, 이렇게 되면 콰인이 아니게 됩니다. 이것을 콰인으로 만드려면 출력에 self_print함수에 대한 정의 자체가 출력에 포함되어야 합니다. 그러기 위해서 두 가지 방법이 있습니다.

  1. self_print함수 내부에 self_print함수를 정의하는 문자열을 넣는다.
  2. self_print함수 외부에 self_print함수를 정의하는 문자열을 넣는다.

이중 1번은 생각해보면 불가능함을 알 수 있습니다. 왜냐하면 self_print함수를 정의하는 데 nn개의 문자가 필요하다고 하면, 이것을 따옴표로 감싸기만 해도 n+2개의 문자가 필요합니다. 그런데 이것이 다시 self_print 함수의 정의에 포함되어야 하므로 n+2<nn+2 < n 이 되어 모순이 발생하기 때문입니다.

따라서 self_print 함수 외부에 그런 문자열이 있어야 하며, 그것을 self_print 내부로 전달해야 합니다. 그러기 위해서 자명히 self_print함수의 파라매터를 사용할 수 있습니다.

그러면 이제 이 함수에 대한 정의를 함수 안에서 알 수 있게 됩니다. 이것을 출력에 포함시키기만 하면 완성입니다. 즉, 다음과 같이 하면 됩니다.

def self_print(x):
    print(f'''{x}

self_print(
    "{x}")''')

이렇게 하면 self_print의 인자로 self_print의 정의를 나타내는 문자열을 받았을 경우 콰인이 됩니다.

다만 이렇게 할 때 사소한 문제는 문자열 escape입니다. 큰따옴표로 선언된 문자열 리터럴 내부에 큰따옴표를 집어넣거나, 줄바꿈을 표현하려면 escape를 해야 합니다. 그런데 print를 사용하여 출력할 때에는 escape 문자가 전부 처리된 후 출력되기 때문에, escape한 문자열을 다시 역-escape해주는 부분을 추가해야 합니다. 그 부분(위의 소스코드에서는 decode함수)위의 소스코드와 같은 결과를 얻게 됩니다.


- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -