C++의 형식 추론과 연역, 귀납, 귀추

Twitter icon류광, 2016-10-25 21:10
C++에서 왜 형식 추론(inference) 대신 형식 연역(deduction)이라는 용어를 더 많이 사용하는지에 관한 한 가지 가설 또는 "꿈보다 해몽".

프로그래머가 형식을 명시적으로 지정하지 않은 코드 요소(주로 변수)의 형식을 컴파일러가 초기치에 근거해서 결정하는 것을 흔히 type inference라고 부릅니다. inference는 흔히 추론이라고 번역하지만, 문맥에 따라서는 ‘추리’라고도 합니다(예를 들어 제 번역서 인공지능: 현대적 접근방식에서는 추론을 inference보다 좀 더 포괄적인 reasoning에게 양보하고, inference는 ‘추리’로 번역했습니다)[1].

그런데 Effective Modern C++에서 저자 스콧 마이어스(SM)는 일관되게 type deduction, 즉 ‘형식 연역’이라는 용어를 사용합니다. 번역서 p.27이나 p.45를 보면 알 수 있지만, SM이 애초에 형식 추론이라는 용어를 몰랐던 것은 아닙니다. 특히, p.45를 보면 type inference가 프로그래밍 세계에서 “흔히 쓰인다”는 점도 알고 있음을 확인할 수 있습니다.

사실 그 책뿐만 아니라, C++ 표준과 관련된 문서들은 거의 예외 없이 deduce/deduction을 사용합니다. C++98, C++03, C++11 표준 명세서에는 infer/inference가 한 번도 등장하지 않고, C++14에는 딱 한 번 등장하는 것으로 알고 있습니다.[2] deduce/deduction은 무수히 많이 나오고요. 애초에 형식 추론에 관련된 C++ 표준 개정 제안 문서들, 이를테면 Deducing the type of variable from its initializer expressionDecltype and auto 등에서 전적으로 deduce/deduction을 사용했으므로 표준 명세서에서 그런 용어를 사용하는 것도 당연한 일입니다. 그렇다고 C++ 표준 개정에 관련된 사람들이 type inference라는 용어의 존재를 몰랐을 것이라고 가정하는 것은 비현실적입니다. 아주 간단한 예로, C++ 표준 위원회(WG21)의 핵심 인물 중 하나인 Herb Sutter는 개인 블로그에 “Type Inference vs. Static/Dynamic Typing”이라는 글을 올린 적이 있습니다.

C++의 형식 추론과 관련해서 일관되게 deduce/deduction이라는 용어를 사용하는 전통(?)은 C++의 창시자 비야네 스트롭스트룹(BS)에서 비롯된 것으로 보입니다. 문자열 검색이 아니라 그냥 페이지를 넘기면서 눈으로 찾은 것이라서 확실하지는 않지만, Design and Evolution of C++의 템플릿 관련 부분에 infer는 없고 deduce만 있습니다(제가 가지고 있는 것은 2004년 판(쇄?)인데, 혹시 1994년 초판 가지고 계신 분은 확인을 좀...) 찾아보기에도 infer나 inference는 없고, deduction과 관련해서는 “deducing template argument”라는 항목이 있습니다.

더 거슬러 올라가서, C++ 템플릿의 역사를 공부하다 보면 BS의 1988년 논문 “Parameterized Types for C++”을 만나게 되는데요. 여기에도 infer/inference는 없고 deduce가 한 번 등장합니다.

C++ 공동체에서 inference 대 deduce에 관한 논의(용어 차원의)는 본 적이 없습니다. 어쩌면 다들 “C++에서 형식 추론은 곧 형식 연역이다”라고 생각하고 있는 게 아닌가 할 정도입니다. 그렇다면 자연스럽게 떠오르는 다음 질문은, “C++에 연역이 아닌 형식 추론, 이를테면 귀납적인 형식 추론은 없을까?”입니다. 다음 예를 봅시다.

auto a = 123.45;

정규 교과과정+일반 대중을 대상으로 한 교양서들 정도로 얻은 지식에 따르면, 연역은 일반적인 것에서 구체적인 것을 끌어내는 추론 방법이고 귀납(induction)은 그 반대 방향, 즉 구체적인 것에서 일반적인 것으로 되돌아가는 것입니다. 그렇다면 위의 예에서 123.45는 구체적인 수치이니까 귀납적인 추론이 아닐까요? 그런데 하나의 사례만으로 어떤 결론을 끌어내는 것은 좀 위험해 보입니다. 그런 해석보다는, “접미사 없는 수치 리터럴의 형식의 double이다”라는 이미 알려진 일반적 원칙에서 출발하는 연역이라고 보는 해석이 더 그럴듯합니다. 실제로 이 해석은 대표적인 연역 방법인 삼단논법에 딱 들어맞습니다.

삼단논법의 예:

초기치 123.45의 형식 추론:

초기치 123.45의 형식에 근거해서 변수 a의 형식을 결정하는 과정 역시 책에 나온 몇 가지 규칙에 따라 연역적으로 일어나고요.

구체적인 값이 둘 이상이면 상황이 달라질까요? 그런 예로 제 머리에 가장 먼저 떠오른 것은 엉뚱하게도[3] 삼항 연산자입니다.

void f(bool cond) 
{
    auto a = cond ? 123.45 : 'c';
}

컴파일러가 123.45'c'를 “함께 고려해서” 하나의 형식을 끌어내는 것이라면 귀납이라고 부를 수도 있겠지만, 실제로는 개별적으로 각각의 형식을 연역하고, 두 형식의 값들을 가능하면 정보의 손실 없이 담을 수 있는 하나의 형식을 찾아내는 것일 뿐입니다. 이 점에서 위의 예는 사실 다음과 별로 다를 바가 없습니다.

auto a = 123.45 + 'c';

그런데 좀 더 생각해 보면(사실 처음부터 깨달아야 했지만), 애초에 귀납 추론은 범용 프로그래밍 언어의 형식 추론에, 특히 C++처럼 컴파일 시점에서 최대한 많은 것을 확인하고 점검하려는 언어의 형식 추론에는 맞지 않는 것 같습니다. 연역은 필연적이지만 귀납은 개연적인 추론 방법이기 때문입니다.

예를 들어 삼단논법(연역)에서 대전제와 소전제가 참이면, 그리고 삼단논법에 맞게 결론을 끌어냈다면, 결론은 무조건 참입니다. 모든 사람이 죽는 게 옳고 소크라테스가 사람인 게 옳다면 소크라테스는 반드시 죽습니다. 반면, 귀납 추론을 삼단논법 비슷하게 표현하면 다음과 같은 형태가 됩니다.

이러한 추론에서, 처음 두 명제가 참이어도 “모든 사람은 죽는다”라는 결론은 허약합니다. 아직 죽지 않은 사람 중에 영원히 사는 사람이 있을 수도 있으니까요.

그리고 귀납과 연역 외에 중요한 추론 방법으로 귀추(abduction)가 있습니다. 귀추법을 소크라테스의 예에 적용하면 이렇습니다.

귀추는 형태상으로 귀납보다 연역에 가깝지만, 결론이 필연적이지 않다는 점은 귀납과 마찬가지입니다. 예를 들어 소크라테스가 죽긴 했지만 사람은 아닐 수도 있으니까요(외계 행성에서 납치(abduction)되어 지구에 온 외계인일 수도...)

정리하자면, 세 가지 주요 추론 방법 중 결론이 필연적인 것은 연역뿐이고, C++에서 형식 추론의 결론은 반드시 필연적이어야 하므로, C++에서 형식 추론은 곧 형식 연역이라고 해도 틀리지 않을 것입니다.

마지막으로, “C++에서 형식 추론의 결론이 반드시 필연적이어야 하는가?”라는 질문이 남아 있는데요. 저는 직관적으로는 “그렇다”라고 생각하지만, 증명이 필요하다면 아마 귀류법(“모순에 의한 증명”)으로 해결할 수 있을 것이라고 생각합니다. 즉, C++ 같은 정적 형식 언어에서 형식 추론의 결과는 ‘유일’해야 하는데(하나의 코드 조각이 두 가지 방식으로 컴파일될 여지가 있어서는 안 되니까요), 만일 추론의 결론이 필연적이지 않다고 가정하면 그 유일성이 깨져서 모순이 발생한다는 점을 보이면 되겠습니다. 혹시 관심 있는 분은 이 증명에 한 번 도전해 보시길!


  1. 예전 제 번역서들에서는 이러한 형식 추론 관련 용어들을 그리 정교하게 구분하지 않았습니다. 글을 쓰면서 살펴보니 2006년까지 나온 C++ 관련 번역서들에서는 type inference를 '형식 유추'라고 번역했는데(아마도 한국 MS의 용어를 참고했던 것 같습니다. 지금도 MSDN 한국어 페이지들에는 type inference가 형식 유추로 되어 있습니다), 유추는 추론의 한 방법인 analogical inference에 해당하므로 그리 좋은 선택이 아니었습니다. 또한, abduction을 귀추가 아니라 유추라고 한 경우도 있고 deduction을 그냥 '추론' 또는 '유도'로 두루뭉술하게 옮긴 책들도 있는데, 조만간 해당 책들의 정오표를 갱신하겠습니다. 

  2. 이 문장은 실제 표준 명세서들(iso.org에서 판매하는)이 아니라 표준 명세서에 가장 가까운 공개 초안들에 근거한 것인데, 실제 표준 명세서와 그에 가장 가까운 초안의 차이는 별로 크지 않으므로 이 문장 역시 거의 틀리지 않을 것입니다. 

  3. 그냥 template<typename T> f(T a, T b)가 더 직접적이라는 점에서. 

태그: 번역 프로그래밍

comments powered by Disqus