1. 개요[편집]
| 원시재귀 함수 Primitive Recursive Function | |
|---|---|
| 기본 함수 | 영함수 · 후속자 $S(x)=x+1$ · 사영 $P^n_i$ |
| 만드는 규칙 | 합성 · 원시재귀 (딱 둘) |
| 프로그래밍 대응 | 횟수가 정해진 for 문만 쓰는 언어 |
| 보장되는 것 | 모든 입력에서 정지하는 전체함수 |
| 못 담는 것 | 아커만 함수 — 전체이고 계산 가능하지만 원시재귀 아님 |
| 확장 | 뮤 연산자(무계 최소화)를 더하면 일반재귀 = 계산 가능 |
| 위계 | 그제고르치크 $\bigcup_n \mathcal{E}^n$ · 빠른 성장 위계의 유한 층 $F_k$ |
안전한 언어를 만드는 방법은 간단하다.
while을 빼면 된다. 문제는 그러면 못 짜는 함수가 생긴다는 것이다.
원시재귀 함수(primitive recursive function)는 영함수·후속자·사영이라는 세 가지 기본 함수에서 출발해 합성과 원시재귀라는 두 가지 규칙만으로 만들어지는 자연수 함수들의 모임이다. 정의가 이렇게 인색한데도 덧셈·곱셈·거듭제곱·나눗셈·소수 판정·정렬·행렬 곱셈까지 우리가 실제로 짜는 알고리즘이 사실상 전부 여기에 들어간다.
이 모임이 수학사에 남은 이유는 담고 있는 것 때문이 아니라 담지 못하는 것 때문이다. 원시재귀 함수는 정의상 모든 입력에서 정지한다 — 무한 루프가 원천적으로 불가능하다. 그런데 “정지하면서 계산 가능한 함수”를 전부 담지는 못한다. 1928년 아커만 함수가 그 반례로 등장하면서, 계산 가능성 ⊋ 원시재귀라는 구분이 확정됐다. 프로그래밍 언어의 말로 옮기면 for 문만으로는 못 짜고 while 문이 있어야 짤 수 있는 함수가 존재한다는 것이고, 이 트레이드오프는 오늘날 정지성을 보장하는 언어와 컴퓨터 보조 증명 도구 설계에서 그대로 되풀이된다.
2. 정의 — 기본 셋과 규칙 둘[편집]
기본 함수는 셋이다.
- 영함수 (혹은 상수 0을 내는 변수 함수)
- 후속자
- 사영
규칙도 둘뿐이다.
합성. 변수 와 변수 함수 에서
원시재귀. 와 에서 마지막 인자를 1씩 줄여 가는 재귀로
두 번째 규칙이 이 체계의 심장이다. 새 값은 바로 직전 값 하나와 현재 카운터로부터만 만들어지고, 카운터는 반드시 1씩 내려간다. 재귀 호출의 깊이가 인자 에 의해 미리 결정되므로 정지성은 에 대한 귀납법 한 줄로 끝난다. 이 모임이 전부 전체함수(total function)인 것은 증명할 무언가가 아니라 정의에서 그냥 따라 나오는 성질이다.
3. 무엇이 만들어지는가[편집]
기본 셋에서 시작하면 금방 익숙한 것들이 나온다.
곱셈에서 거듭제곱, 거듭제곱에서 계승이 같은 방식으로 나온다. 한 층 위의 연산은 아래 층 연산의 반복이라는 구조가 여기서 이미 보이는데, 이 사다리를 함수 하나에 통째로 집어넣으면 아커만 함수가 된다는 점이 나중에 중요해진다.
조건 분기도 만들 수 있다. 열쇠는 잘린 뺄셈(monus) 이고, 이것 역시 전임자 함수의 원시재귀로 정의된다. 여기서 부호 함수 , 비교 술어, if-then-else 가 전부 따라 나온다. 술어는 0/1 값을 내는 함수로 다루면 되므로 논리곱·논리합·부정도 산술로 표현된다.
더 놀라운 것은 유계 연산이 전부 원시재귀라는 사실이다.
- 유계 합 , 유계 곱
- 유계 최소화 — ” 이하에서 를 만족하는 최소의 , 없으면 ”
유계 최소화가 있다는 것은 탐색 범위에 상한을 미리 댈 수만 있으면 탐색도 원시재귀라는 뜻이다. 나눗셈·나머지·약수 판정·소수 판정· 번째 소수 모두 여기서 나온다. 괴델의 β 함수나 소인수분해를 이용한 수열 부호화를 쓰면 유한 수열 전체를 자연수 하나에 넣고 꺼내는 일도 원시재귀가 되므로, 리스트·스택·배열을 쓰는 알고리즘도 결국 이 안이다.
정리하면 이렇다. 실무 알고리즘 중 “얼마나 오래 걸릴지 입력만 보고 상한을 댈 수 있는” 것은 전부 원시재귀다. 정렬, 행렬 연산, 동적 계획법, 그래프 탐색, 수치적분 루틴, 고정 반복 횟수의 선형계 풀이 — 다 들어간다.
4. for 문과 while 문[편집]
이 대응이 이 문서에서 가장 실용적인 부분이다. 마이어와 리치(1967)의 LOOP 언어는 변수 대입, 증감, 그리고 딱 한 종류의 반복문만 갖는다.
LOOP x DO ... END
의미는 “몸통을 번 실행한다”이고, 결정적인 제약은 몸통 안에서 를 바꿔도 반복 횟수가 안 바뀐다는 것이다. 횟수가 진입 시점에 확정된다. 이 언어로 계산 가능한 함수의 모임이 정확히 원시재귀 함수다.1
여기에 WHILE x ≠ 0 DO ... END 를 추가하면 언어의 표현력이 튜링 기계와 같아진다. 함수 쪽에서 이에 대응하는 것이 뮤 연산자(무계 최소화)다.
” 를 0으로 만드는 최소의 ”. 유계 버전과 딱 하나가 다른데, 상한이 없다. 그런 가 없으면 이 함수는 값을 갖지 않고, 계산은 영원히 돈다. 전체함수가 부분함수(partial function)로 바뀌는 순간이 바로 이 지점이고, 정지 여부를 미리 알 수 없게 되는 것도 정확히 이 지점이다. 원시재귀 함수에 뮤 연산자를 더한 것이 부분재귀 함수(= 튜링 계산 가능)이고, 처치-튜링 논제가 말하는 “계산 가능”이 이것이다.
뮤 연산자가 얼마나 필요한지에 대한 정확한 답도 있다. 클레이니 정규형 정리에 따르면 임의의 계산 가능한 부분함수는
꼴로 쓸 수 있고, 여기서 술어 와 함수 는 원시재귀다. 즉 무계 탐색은 딱 한 번이면 충분하다. “프로그램 가 입력 에서 라는 계산 기록으로 정지한다”를 검사하는 일 자체는 유한하고 원시재귀적이며, 계산 불가능성은 전부 “그런 를 찾을 때까지 세는” 바깥쪽 루프 하나에 응축된다. 정지 문제의 난이도가 어디서 오는지를 이보다 깔끔하게 보여 주는 진술은 없다.
5. 담지 못하는 것[편집]
원시재귀가 계산 가능성 전체보다 진짜로 작다는 증명은 두 가지 맛이 있다.
대각화(추상적). 원시재귀 함수는 유한한 구성 기술(記述)을 가지므로 그 기술들을 기계적으로 나열할 수 있고, 번째 원시재귀 1변수 함수를 이라 쓸 수 있다. 이제
을 정의하면 는 명백히 계산 가능하고 모든 입력에서 정지하지만, 어떤 에 대해서도 이다( 에서 값이 다르다). 따라서 는 원시재귀가 아니다.2
아커만(구체적). 대각화는 존재만 보여 줄 뿐 “그래서 그게 뭔데”에 답하지 않는다. 아커만이 준 것은 세 줄로 적히는 실제 함수다. 임의의 원시재귀 함수 에 대해 어떤 이 존재해 이 모든 에서 성립한다는 정리가 핵심이고, 여기에 을 대각선으로 넣으면 모순이 나온다. 증명의 뼈대와 그 함수가 실무에서 역함수로 되돌아오는 이야기는 아커만 함수 문서에 있다.
여기서 오해를 하나 못 박아 두자. “원시재귀가 아니다”는 “계산할 수 없다”가 아니다. 아커만 함수는 계산 가능하고 언제나 정지한다. 못 하는 것은 반복 횟수를 미리 아는 루프만으로 짜는 것뿐이다. 정지 문제의 판정 함수가 계산 불가능한 것과는 층이 완전히 다르다.
6. 증가 속도로 본 지형[편집]
원시재귀 안쪽도 평평하지 않다. 그제고르치크 위계 는 허용되는 증가 속도의 상한을 층마다 한 칸씩 올려 쌓은 것으로, 합집합이 정확히 원시재귀 함수 전체다. LOOP 언어로 말하면 반복문의 중첩 깊이가 위계의 층에 대응한다. 삼중 루프로 짤 수 있는 것과 사중 루프가 필요한 것이 진짜로 다르다는 뜻이다.
은 칼마르 초등함수(elementary function)로 따로 이름이 있다. 덧셈·곱셈·거듭제곱과 유계 연산으로 닫힌 모임이며, 처럼 높이가 고정된 거듭제곱 탑까지는 담지만 높이가 에 따라 자라는 탑은 못 담는다. 실무 알고리즘은 거의 전부 이 아래 두세 층 안에서 끝난다.
빠른 성장 위계(fast-growing hierarchy)는 같은 지형을 순서수로 매긴다.
( 를 번 합성한다는 뜻이다.) , 는 지수, 은 거듭제곱 탑이고, 원시재귀 함수는 정확히 어떤 유한한 의 에 눌리는 함수들이다. 그리고 극한 자리의
이 본질적으로 아커만 함수다. 아커만이 원시재귀 밖에 있는 이유가 위계 그림에서는 한눈에 보인다 — 그것은 모든 유한 층 위에 걸터앉은 대각선이다.
이 사다리는 원시재귀 위로도 계속 올라간다. 근처에 굿스타인 정리의 수열 길이 함수와 파리스-해링턴 함수가 있는데, 이들은 전체함수이고 계산 가능하지만 페아노 산술로는 정지성을 증명할 수 없다. 계산 가능성과 증명 가능성이 갈라지는 또 다른 층이 여기 있다.
7. 왜 이게 실무에 걸리나[편집]
- 정지성 검사기의 정체. Coq/Rocq, Agda, Lean 같은 증명 보조기는 재귀 정의를 받을 때 인자가 구조적으로 줄어드는지를 기계적으로 확인한다. 이 검사가 통과시키는 것이 대체로 원시재귀(를 고차 타입으로 일반화한 것)의 모양이고, 더 복잡한 재귀는 잘 정초된 관계를 사람이 직접 제시해야 한다. “정지가 보장되는 언어는 표현력을 잃는다”는 1928년의 교훈이 매일의 컴파일 에러로 되돌아오는 셈이다.3
- 연료(fuel) 패턴. 전종료 언어에서 while 루프를 흉내 낼 때 쓰는 국룰은 반복 횟수 상한을 인자로 하나 더 받는 것이다.
fuel이 0이 되면 기본값을 반환한다. 원시재귀로 내려앉히는 가장 정직한 방법이고, “상한만 댈 수 있으면 원시재귀”라는 위 원칙을 코드로 옮긴 것이다. - 원시재귀는 효율성의 보증이 전혀 아니다. 초등함수 층에 이미 거듭제곱 탑이 들어 있다. “이 함수는 원시재귀다”는 끝나기는 한다는 말이지 실용적이라는 말이 아니며, 실제 성능을 말하려면 계산 복잡도 쪽 어휘가 필요하다. 이론적 안전성과 공학적 실행 가능성을 헷갈리면 딱 여기서 사고가 난다.
- 형식주의의 유산. 힐베르트 학파가 “이의 없이 받아들일 수 있는 유한적 추론”의 후보로 잡은 것이 원시재귀 산술(PRA)이었다. 겐첸이 1936년 페아노 산술의 무모순성을 PRA + 까지의 초한귀납으로 증명한 것이 그 프로그램의 마지막 형태다. 오늘날 증명 보조기의 신뢰 기반 논의가 “커널을 얼마나 작게 만들 것인가”로 굴러가는 것은 그 문제의식의 후손이다.
8. 여담[편집]
- 아커만 함수가 반례로서 특별한 이유는 자연스럽기 때문이다. 대각화로 만든 은 “원시재귀가 아니도록 일부러 설계된” 물건이라 누구도 감동하지 않는다. 반면 아커만은 덧셈→곱셈→거듭제곱이라는 누구나 아는 사다리를 한 칸씩 올라가는 함수이고, 그게 밖으로 튀어나간다.
- 이 개념의 이름이 오해를 부른다. “원시(primitive)“는 원초적·단순하다는 뜻이지 열등하다는 뜻이 아니고, “재귀(recursive)“는 1930년대 어휘라 오늘날의 “계산 가능”에 가깝다. 그래서 옛 문헌의 “일반재귀 함수(general recursive function)“는 재귀를 일반적으로 쓴다는 뜻이 아니라 계산 가능한 전체함수를 가리킨다.
- 원시재귀 함수를 전부 열거할 수 있다는 사실 자체가 꽤 킹받는 성질이다. 프로그램을 나열할 수 있으면서 그 프로그램들이 전부 정지한다면, 대각선은 언제나 밖에 있다. “모든 프로그램이 정지하는 언어”는 필연적으로 불완전하다는 것이 이 논증의 일반형이고, 전종료 언어를 설계할 때마다 재발견된다.
9. 관련 문서[편집]
10. Footnotes[편집]
-
이 제약이 얼마나 빡센지는 C의
for와 비교하면 드러난다.for (i=0; i<n; i++)는 몸통에서n이나i를 건드리면 언제 끝날지 모르게 되므로 LOOP 문이 아니라 사실상while이다. 진짜 LOOP 는 파이썬의for i in range(n)처럼 반복자를 진입할 때 확정하고 몸통이 못 건드리는 형태다. 그러니까 “나는 while 을 안 쓰니까 안전하다”는 주장은 그 for 문이 어느 쪽인지에 달려 있다. ↩ -
이 논증이 성립하려면 ” 을 나열하는 일”과 ” 을 계산하는 일”이 둘 다 기계적으로 가능해야 한다. 원시재귀 함수의 기술은 유한한 구문 트리라 나열이 되고, 그 트리를 해석하는 범용 계산기는 원시재귀가 아니지만(당연하다) 계산 가능하기는 하다. 이 미묘함이 증명의 전부이고, 학부 강의에서 학생이 가장 자주 미끄러지는 지점이기도 하다. ↩
-
여기에 재미있는 반전이 있다. 괴델의 체계 T는 고차 타입에서의 원시재귀를 허용하는데, 이렇게 하면 아커만 함수가 정의된다. 자연수 위에서는 원시재귀가 아커만을 못 담지만, 함수를 반환하는 함수 층위로 올라가면 담긴다는 뜻. “원시재귀”라는 울타리가 타입 층위에 따라 위치를 바꾼다는 점에서, 증명 보조기의 정지성 검사기가 생각보다 넉넉한 것도 같은 이유다. ↩