튜링상 관련 업적
컴퓨터가 세상에 등장한 이래로 소프트웨어 프로그램을 작성하는 일은 항상 도전적이었다. 만들고 싶은 목표와, 이를 실현해 주는 도구 사이에는 언제나 괴리가 있었다. 그 간극을 메워주기 위해 도구를 발전시키려는 노력이 부단히 이루어졌다.
대표적인 도구가 프로그래밍 언어이다. 기계어와 어셈블리어는 사용하기가 너무 불편하고 힘들었다. 1950년대 중반에 포트란이 등장하면서 개선의 실마리가 보였다. 1950년대 말에서 1960년대 초로 넘어가면서 알골, 리스프, APL 같은 굵직한 진전이 이루어졌다.
알골 언어는 어셈블리어 프로그래밍의 방식을 자연스럽게 연장하려는 노력의 결과였다. 기계어 명령어에 존재하는 대입, 비교, 분기의 개념을 그대로 가져왔고, 어셈블리어 프로그래밍에서 널리 사용되던 서브루틴 기법도 적극 받아들였다. 그래서 현대 프로그래밍 언어의 시발점이 되었다.
그런데 컴퓨팅 공동체에서 알골 언어는 실질적인 언어로 자리매김하지는 못했다. 생명력을 가지고 널리 사용된 언어를 손꼽으라면 포트란, 코볼, 파스칼, C, 자바, PHP, 자바스크립트 등을 떠올릴 수 있다. 대부분의 언어들이 특정 기업이나 개인에 의해서 개발되어 공개되었다. 이 언어들은 명세서가 공개된 후에 개발이 이루어진 것이 아니라, 개발이 모두 끝난 후에 명세서가 공개되었다. 이에 비해 알골, 에이다Ada 같은 언어들은 위원회를 구성하여 공개적으로 개발되었으나 현장에서는 존재감을 가지지 못했다. 프로그래밍 언어뿐만 아니라 데이터베이스, 네트워크 분야에서도 위원회를 통해 추진된 기술은 하나의 참고 모형으로만 살아남고 현실에서는 빛을 보지 못한 사례가 많다.
알골 60 언어는 당대의 프로그래밍 전문가들이 참여하여 설계했다. 그래서 참고 모형으로는 훌륭한 역할을 했지만 막상 알골 60 언어로 프로그래밍을 하는 이는 드물었다. 왜냐하면 컴파일러가 제때 나와주지 않았다. 그저 여기저기에서 각자 필요에 따라 제한적인 컴파일러를 만드는 경우가 많았다. 그리고 이때는 응용프로그램이 수치계산이나 비즈니스 정보처리 위주였으므로 알골 60 컴파일러에 대한 수요도 그리 많지는 않았을 듯싶다. 그러는 와중에 IBM에서 PL/I이라는 언어를 발표하면서 현장용 프로그래밍 언어의 선택지에서 밀려나고 말았다.
1960년대 초에는 데이터베이스 관리 시스템이 등장했고 1960년대 중반에서 후반까지는 시분할 시스템 개발이 뜨거운 주제였다. 데이터베이스 관리 시스템은 메모리 자원의 제약과 효율 때문에 어셈블리어로 짰고, 시분할 시스템 개발에서는 PL/I이 사용되거나 BCPL 등이 사용되었다.¶ 그러다가 유닉스와 함께 1970년대 초에 등장한 C 언어가 대세로 자리 잡는다.
PL/I 컴파일러가 처음 공개된 것이 1966년이고 BCPL 컴파일러가 등장한 것은 1967년이다. PL/I 컴파일러는 IBM이 공급하는 것이므로 대학에서 사용하기에는 부담스러웠을 것이고 BCPL은 연구소 내에서 사용되는 수준이었다. 따라서 1967년에 스위스에서 컴퓨터 프로그래밍을 가르쳐야 했던 니콜라스 비르트 입장에서는 교육용으로 사용할 만한 언어가 마땅치 않았을 것이다.
파스칼
파스칼Pascal 언어는 알골 60 언어와 거의 동일하다. 문법적으로 약간의 변화가 있을 뿐 알골 60 언어가 가진 아래의 특징을 그대로 계승하고 있다.
- 선언문declaration
- 타입type
- 대입문
- 조건문
- 반복문
- 블럭 구조block structure
- 프로시저procedure
- 내포되는 프로시저nested procedure
- Call by value
- 재귀적 호출recursion
알골 60 언어에 비해 달라진 점은 다음과 같다.
복합 자료구조 지원
알골 60 언어가 지원하는 자료 구조는 간단했다. 스칼라 타입scalar type으로는 불린boolean, 정수, 실수, 문자열이 있었고, 정수와 실수는 배열 타입이 가능했다.
파스칼 언어에서는 레코드 타입record type이라는 것이 추가되었다. 하나의 타입 내에 서로 다른 타입을 가지는 서브타입subtype이 가능했다. 예를 들면 다음과 같은 것이다.
TYPE
a = RECORD
x : integer;
y : char
END;
이는 C 언어의 구조체Structure와 유사하다.#
강타입
알골 60 언어는 정적 타입static type을 사용한다. 이 말은 컴파일할 때 변수의 타입이 이미 정해져 있다는 의미이다. 프로그램 내에서 변수를 선언할 때, 정수인지 실수인지를 명시적으로 표시하므로 그러하다. 하지만 강타입strong type 인지 아닌지는 명확하게 정해놓지 않았다. 강타입이란, 컴파일 단계에서 타입 오류를 엄격하게 검사하는 것을 말한다. 알골 60 언어는 공식 컴파일러를 만들지 않았기 때문에 사실상 강타입 여부를 따지기 어렵다.
파스칼 언어는 비르트가 직접 컴파일러를 만들었기 때문에 확실하다. 비르트는 타입 오류를 엄격하게 검사하려 했다. 그래서 강타입 언어라고 부른다. 어떤 경우에 타입 오류가 발생할까? 다음의 예를 보자.
VAR
x : integer;
y : real;
BEGIN
y := 1;
x := y;
IF ( y > 1 ) THEN writeln(y);
END.
위에는 세 개의 타입 오류가 있다. 첫 번째는 변수 y에 정수 1을 대입하려 할 때이다. 변수 y의 타입이 실수이므로 타입이 서로 어긋난다. 두 번째는 변수 x에 변수 y의 값을 대입하려 할 때이다. 두 변수의 타입이 서로 다르기 때문이다. 세 번째는 IF 문에서 변수 y와 정수 1을 비교할 때이다. 둘은 타입이 다르기 때문에 비교할 수 없다.
어떤 프로그래밍 언어들은 타입을 깐깐하게 따지지 않기도 한다. 위의 예를 C 언어로 작성하면 다음과 같다.
int x;
float y;
y = 1;
x = y;
if ( y > 1 ) printf(y);
C 컴파일러는 위의 코드에 대해 타입 오류가 있다고 하지 않는다.
Call by reference
알골 60 언어에서 프로시저를 호출할 때 인자값을 넘겨주는 방법은 call by value와 call by name이 있다. Call by value는 값을 전달하기 때문에 프로시저 내에서 그 값으로 무슨 짓을 하든지 간에 그 프로시저 바깥에 있는 코드에는 영향을 주지 않는다. 이에 비해 call by name은 인자의 ‘값’이 아니라 인자의 ‘이름’을 전달하는 것이다. 그래서 프로시저 내에서 그 인자의 이름으로 뭔 짓을 하게 되면 그 프로시저 바깥에 있는 같은 이름의 변수가 그대로 영향을 받게 된다.
Call by reference는 call by name과 유사한 효과를 가진다. 차이가 있다면 인자의 ‘이름’이 아니라 ‘주소’가 전달된다는 점이다. 사실 현실적으로 call by name은 컴파일러에 구현하기가 쉽지 않다. Call by reference에서는 프로시저 내에서 그 ‘주소’에 대해 뭔 짓을 하게 되면 해당 주소의 메모리에 저장된 값이 변하게 되므로 자연스럽게 그 영향은 프로시저 바깥에도 나타나게 된다.
다음은 파스칼 언어로 작성한 버블 정렬bubble sort 프로그램의 예이다.6
PROGRAM Sort(input, output);
CONST
(* Max array size. *)
MaxElts = 50;
TYPE
(* Type of the element array. *)
IntArrType = ARRAY [1..MaxElts] OF integer;
VAR
(* Indexes, exchange temp, array size. *)
i, j, tmp, size: integer;
(* Array of ints *)
arr: IntArrType;
(* Read in the integers. *)
PROCEDURE ReadArr(VAR size: integer; VAR a: IntArrType);
BEGIN
size := 1;
WHILE NOT eof DO BEGIN
readln(a[size]);
IF NOT eof THEN
size := size + 1
END
END;
BEGIN
(* Read *)
ReadArr(size, arr);
(* Sort using bubble sort. *)
FOR i := size - 1 DOWNTO 1 DO
FOR j := 1 TO i DO
IF arr[j] > arr[j + 1] THEN BEGIN
tmp := arr[j];
arr[j] := arr[j + 1];
arr[j + 1] := tmp;
END;
(* Print. *)
FOR i := 1 TO size DO
writeln(arr[i])
END.
위의 예를 보면 프로그램 전체를 감싸는 선언문인 Program으로 시작된다. Sort라는 이름의 이 프로그램은 크게 다섯 토막으로 나뉘어 있다. 맨 처음 네 토막은 모두 선언문이고 맨 마지막 토막이 메인 블럭main block이다. 맨 처음 네 개의 선언문은 상수(CONST), 타입(TYPE), 변수(VAR), 프로시저(PROCEDURE)를 각각 선언하고 있다.
프로시저인 ReadArr은 두 개의 인자를 사용한다. 둘 다 VAR이라는 예약어가 앞에 붙어 있다. 이것은 이 인자가 call by reference 임을 의미한다. 즉, 이 인자는 포인터이므로 이 인자의 값을 수정하면 메인 블럭에 있는 해당 변수의 값도 함께 변하는 셈이 된다.
한 가지 주의할 점이 있다. readln()과 writeln()은 프로그램 내에 선언되어 있지 않음에도 사용하고 있다. 이는 일종의 시스템이 제공하는 함수로서 명시적으로 선언되지 않아도 사용이 가능하다.
파스칼 컴파일러는 파스칼 언어로 작성된 것으로도 유명하다. 언뜻 들으면 말이 안 되는 듯 들린다. 파스칼 컴파일러를 파스칼 언어로 만들었으면 그것은 무엇으로 컴파일했단 말인가? 최초의 파스칼 컴파일러는 다른 언어Scallop를 사용해서 만들었고 손으로 컴파일하기도 했다고 한다.7 그렇게 해서 첫 컴파일러가 만들어진 후에는 컴파일러를 수정할 때마다 기존의 파스칼 컴파일러로 컴파일했다. 비르트는 이것이 프로그래밍 언어를 시험하는 가장 강력하면서 기초적인 방법이라고 생각했다.4
답글 남기기