알골 언어의 특징
컴퓨터 세계는 결과물이 정의하는 세상이다. PC, 이더넷, 마우스 등 우리가 흔히 사용하는 컴퓨터 용어들은 대부분 성공한 제품의 이름이다.
알골이라는 이름의 프로그래밍 언어를 아는 이는 드물다. 가장 큰 이유는 알골이라는 프로그래밍 언어가 실제로 현장에서 사용된 경우가 거의 없기 때문이다. 알골은 제품 이름이 아니라 프로그래밍 언어 명세specification였다. 이 명세를 만족하는 언어를 만들려는 노력은 많았으나 알골이라는 이름을 달고 제품으로 성공하지는 못했다. 그에 대한 이유는 여러가지가 있겠으나 가장 큰 이유는 알골 설계자들이 의도적으로 플랫폼에 비종속적인 언어를 만들고 싶어했기 때문이었다. 그래서 알골 언어의 명세서를 들여다보면 많은 좋은 개념들이 잘 정리되어 있지만 결정적인 단점이 존재한다. 다름 아닌 입출력에 대한 정의가 빠진 것이다.
21세기에 들어와서 컴퓨터 언어를 배우는 사람이 반드시 한 번은 거치게 되는 코드가 있다. 다름 아닌 “Hello World!” 코드이다. 파이썬을 배우건 자바스크립트를 배우건 간에 대부분의 입문서는 맨 처음 맛보기 코드로 “Hello World!” 출력 프로그램을 포함하고 있다. 그만큼 사용자의 눈에 뭔가 보여주는 일은 컴퓨터 프로그램에서 없어서는 안 되는 부분이다. 입출력을 고려하지 않은 프로그래밍 언어는 쓸모가 없는 셈이다.
결국 알골은 하나의 제품으로 개발되기에는 결정적인 약점을 가지고 있었다. 하지만 알골이 정리한 언어적 개념들은 매우 유용했다. 그래서 알골은 많은 파생 언어를 만들어냈고 이 언어들이 현대의 여러 프로그래밍 언어들로 발전했다. 우리는 이런 언어를 알골스러운 언어(ALGOL-like 언어)라고 부른다. 알골스러운 언어를 나열하자면 다음과 같다.
- PASCAL
- PL/I
- SIMULA
- BCPL
- C
- Visual Basic
- Java
- Python
다음의 코드는 알골 60의 명세를 따라 작성한 예이다. 이 예를 보면 알골이 담고 있는 언어적 특징을 알 수 있다.##
procedure Absmax(a, n, m, y, i, k);
value n, m;
array a; integer n, m, i, k; real y;
comment The absolute greatest element of the matrix a, of size n by m
is transferred to y, and the subscripts of this element to i and k;
begin
integer p, q;
y := 0; i := k := 1;
for p := 1 step 1 until n do
for q := 1 step 1 until m do
if abs(a[p, q]) > y then
begin
y := abs(a[p, q]);
i := p; k := q
end
end Absmax
위의 프로그램 코드는 Absmax라는 이름의 프로시저procedure를 보여주고 있다. 어떤 부분이 현대 컴퓨터 프로그래밍 언어의 특징을 보여주고 있는지 하나씩 따져 보자.9
세미콜론으로 문장 종료
고급 프로그래밍 언어의 프로그램은 결국 명령 문장statement들이 나열되는 형태이다. 그리고 문장은 약속된 문법을 따르게 되어 있다. 따라야 하는 문법 중 하나가 문장과 문장을 구분짓기 위한 표시이다. 우리가 일상생활에서 사용하는 언어의 경우에는 대부분의 경우 마침표가 찍히면 문장이 끝났다고 본다. 컴퓨터 프로그램에서도 이와 유사하게 문장이 끝났음을 표현할 방법이 필요하다.
어셈블리 언어에서는 기계어가 한 줄로 표현되므로 줄이 바뀌면 당연히 다른 명령문장으로 간주한다. 그리고 포트란에서도 줄이 바뀌면 다른 명령문장으로 간주한다.***
하지만 알골에서는 명령문장의 마지막 위치를 명시적으로 표현하도록 정했다. 그 용도로 선택된 문자가 세미콜론이다. 즉 세미콜론(;)이 오면 그 다음부터는 새로운 명령 문장인 것으로 해석된다. 이 약속은 이후의 거의 모든 프로그래밍 언어에서 채택되었다. 대표적으로 C언어가 있다.
근래에 들어서는 다시 과거로 회귀하는 모습을 보여주고 있다. 대표적으로 파이썬의 경우는 단순문의 경우, 줄 바꿈이 있으면 명령문장이 새로 시작하는 것으로 간주한다.
선언문
새로운 식별자identifier를 사용하기 위해서는 먼저 그 식별자가 어떤 특성을 가지고 있는지를 명시적으로 나타내야 한다. 이것을 선언declaration이라고 한다. (현실적으로 프로그래밍 언어는 영어로 작성하므로 이후의 모든 설명은 영어를 기준으로 함에 유의하기 바란다.)
일단 고급 언어로 작성한 프로그램은 형태적으로 보면, 영어 단어들의 집합이다. 이 영어 단어들은 특정 약속에 따라 배열된다. 이를 문법syntax이라고 한다. 문법에 어긋난 배열은 오류로 처리되며 기계어로 변환되지 못한다.
각 영어 단어는 의미를 가진다. 크게 두 가지로 나뉘는데 이미 처음부터 의미가 고정된 단어와, 프로그래머가 의미를 부여한 단어로 나눌 수 있다. 전자를 예약어라고 부른다. 이 예약어는 이미 의미와 용도가 정해져 있으므로 거기에 맞게 사용해야 한다. 위의 예제 코드에서 procedure, comment, begin, integer, for, if, end 등의 단어는 예약어이다. 이 단어가 무엇을 의미하는지는 프로그래밍 언어의 명세서를 읽어보면 알 수 있다.
위의 예제 코드에서 Absmax, p, q, n, m 등의 단어는 예약어가 아니다. 즉 원래 정해진 의미가 있는 것이 아니라 프로그래머가 의미와 용도를 부여한 것이다. 그렇다면 이 단어는 어떤 의미와 용도를 가지고 있는가? 바로 이것을 알려주는 것이 선언declaration이다. 선언이라는 명시적이라는 형태를 통해서 프로그래머가 사용할 새로운 단어의 의미와 용도가 정해진다.
위의 예에서 Absmax는 procedure라는 예약어 뒤에 왔다. 이는 Absmax가 procedure의 이름임을 명시적으로 표현한 것이다. p와 q의 경우는 integer라는 예약어 뒤에 왔다. p와 q가 정수형 타입type을 가지는 변수의 이름임을 명시적으로 표현한 것이다. 이렇게 명시적으로 선언되지 않은 단어들은 프로그램 내에서 사용될 수 없다. 컴파일러는 이를 자동으로 검출해 내어 오류로 처리한다.
타입
타입type은 컴퓨터가 다루는 데이터의 종류를 구별하기 위한 방법이다. 컴퓨터가 다루는 데이터는 여러 가지 종류가 있다. 처음에 컴퓨터가 만들어졌을 때는 데이터가 아마도 한 가지 종류만 있었을 것이다. 다름 아닌 숫자이다. 원래 방정식을 계산하기 위해 만든 것이 컴퓨터였으므로 컴퓨터가 다루는 데이터는 숫자였다. 숫자도 따지고 들어가면 정수, 실수, 복소수 등 여러 종류가 있는데 포탄의 궤적을 계산하기 위한 용도였으므로 실수 계산이 주목적이었을 것으로 추측된다.
하지만 컴퓨터의 용도가 확장되면서 다루는 데이터의 종류도 확장될 수밖에 없다. 예를 들어 인구 통계를 계산하는 프로그램이라면 실수보다는 정수를 다룰 일이 더 많다. 조직 관리를 위한 프로그램이라면 사람과 조직의 이름을 위해서 문자열을 다루어야 한다. 또한, 찬반 투표를 처리하는 프로그램이라면 찬성 혹은 반대라는 두 가지 값만을 가지는 데이터를 다루게 된다.
이렇게 데이터의 형태는 다양하지만 결국 컴퓨터 내부에서는 이진수라는 단일한 방식으로 표현되어야 한다. 따라서 기계어로 프로그래밍을 하게 된다면 데이터가 정수integer이건 문자열string이건 간에 모두 이진수로 바꾸어 프로그램에 표현할 수밖에 없다. 매우 골치 아픈 일이 아닐 수 없다.
기계어 프로그래밍의 어려움을 덜어주려는 것이 고급 프로그래밍 언어라고 한다면, 고급 프로그래밍 언어에서는 다양한 종류의 데이터들을 직관적으로 사용할 수 있게 지원해 줄 필요가 있다. 정수 1은 1로 표현하고, 실수 -2.34는 -2.34라고 프로그램에 표현할 수 있다면 아주 좋을 것이다. 컴파일러는 정수 1과 실수 -2.34를 각각 이진수로 변환하면 된다. 그런데 보기에는 숫자처럼 보이지만 실제로는 문자열로 사용되는 경우도 있다. 예를 들어 연도에 사용되는 숫자는 문자열로 다룰 때가 있다. 만약 프로그램에 2022라는 데이터가 나타났을 때 컴파일러는 이것이 숫자 2022인지 문자열 2022인지 어떻게 판단할 수 있을까? 이런 모호한 경우를 피하기 위해서는 프로그램에 사용되는 데이터마다 어떤 종류에 해당하는지를 명시적으로 지정해줄 필요가 있다. 그래서 나온 것이 타입이라는 개념이다. 데이터가 어떤 종류에 속하는지를 명시적으로 표현하는 것이다.
위의 예에서는 세 가지 종류의 타입을 볼 수 있다. n, m, i, k, p, q는 정수형integer 타입이고 y는 실수형real 타입이다. 그리고 a는 배열array이라는 타입이다. 우리는 n과 y와 a가 서로 다른 타입임을 알 수 있다. 각각 어떻게 다른지는 프로그래밍 언어의 명세를 확인해야 한다. 어찌되었건 간에 서로 다른 타입끼리는 함부로 계산에 혼용할 수 없다. 그것이 타입을 사용하는 또 다른 이유이다.
대입문
대입문assignment statement은 프로그램의 가장 기초적인 단위에 해당한다. 사실상 프로그램이란 대입문들의 연속이라고 볼 수도 있다. 이는 초창기 프로그램이 수학 계산용으로 만들어졌기 때문이기도 하다. 수학 계산은 등호의 연속 아니던가?
x = 1
위와 같은 표현이 수학 계산에 있다면 이는 변수 x의 값이 1임을 표시한다. 암묵적으로 우리는 1은 상수를 의미하며 따라서 1의 값이 x라는 식으로는 생각하지 않는다. 이는 프로그래밍 언어에서도 마찬가지이다. 하지만 약간의 차이가 있다. 수학은 근본적으로 추상적이어서 x에게 어떤 물리적인 의미를 부여하지 않는다. 하지만 프로그래밍 언어에서는 다르다. x는 물리적으로 존재하는 실체이다. 그것은 메모리에 존재한다. 이는 우리의 컴퓨터 시스템이 폰 노이만 구조를 따르고 있기 때문이다.
또한 수학에서는 등호가 단지 ‘동일하다’는 의미를 가지지만 프로그램에서는 대입(assignment), 혹은 저장(store)의 의미를 가진다. 위의 표현이 컴퓨터 프로그램에서는 x라는 변수 공간에 1이라는 값이 저장된다고 해석된다.
x = y + 1
그렇다면 위의 표현은 어떻게 해석되어야 할까? 수학에서는 x와 y+1이 동일함을 의미한다. 그래서 x가 나타나는 자리에는 y+1이 대신 들어갈 수 있음을 의미한다. 컴퓨터 프로그램에서는 그렇지 않다. 항상 등호의 오른편은 구체적인 값(value)을 가져야 한다. 위의 표현이 프로그램에서는 y+1을 계산한 값이 x라는 공간에 저장된다고 이해된다. 왜냐고? 그렇게 하기로 약속했기 때문이다. 알골을 설계한 이들이 그렇게 하기로 약속했기 때문이다.
여기서 다시 한 번 알골 58 회의로 돌아가보자. x라는 변수에 1이라는 값을 대입하는 코드를 작성한다고 할 때 이를 어떻게 표현할 것인가를 놓고 격론이 벌어졌다. 미국 측에서는 등호 오른쪽의 값이 등호 왼쪽의 변수에 대입되는 형태를 원했고 유럽 측에서는 정반대의 형태를 원했다. 결국은 미국 측이 원하는 형태로 이루어졌다. 만약 유럽 측의 뜻이 관철되었다면 우리는 지금 아래과 같이 코드를 짜고 있을지 모른다.
1 = x;
x + 1 = y;
조건문
인생은 선택의 연속이라는 말이다. 선택이란 두 개 이상 중 하나를 고르는 행위를 말한다. 프로그램도 마찬가지이다. 그냥 직진하는 프로그램은 존재하지 않는다. 중간에 여러 번 선택의 순간을 맞이한다. 이를 위해 있는 것이 조건문conditional statement이다.
조건문은 프로그래밍이 시작되었을 때부터 존재했을 것이다. 심지어 최초의 프로그래밍 언어라고 할 수 있는 Autocode에서도 조건문이 나왔다. 하지만 if라는 단어를 최초로 사용한 언어는 포트란이다. 그런데 포트란의 if문은 조금 특이했다. 수치계산을 목적으로 만든 언어였으므로 포트란의 if문은 참, 거짓을 따지는 것이 아니라 숫자의 크고 작음을 따졌다.
IF (e) s1, s2, s3
위의 예에서 e의 값이 0보다 작으면 s1, 0이면 s2, 0보다 크면 s3을 실행한다.
알골의 if문은 논리적인 if문이었다.
if e then s1
e의 값이 참이면 s1을 실행한다. 알골의 논리적 if문이 현대의 if문으로 살아남았다. 처음에는 if then만 있었지만 후에 slse가 추가되었다.
반복문
반복문loop statement은 포트란에서 이미 등장했다.
DO n = 1, 10
nfact = nfact * n
END DO
우리가 현재 흔히 보게 되는 for loop 문은 알골에서 처음 도입되었다.
for n := 1 step 1 until 10 do
nfact = nfact * n;
복합문(블럭)
알골에서 새롭게 제시된 가장 중요한 특징이 복합문compound statement의 도입이다. 예전 같으면 한 개의 명령문장statement만 올 수 있는 자리에 여러 개의 명령문장들statements이 올 수 있게 된 것이다. 이로 인해 스코프scope라는 개념이 후에 생겨나게 된다.
알골에서 복합문은 복수개의 명령문장들을 begin과 end로 묶어주는 형태를 의미한다. C언어에서는 { 와 } 를 사용하고 파이썬에서는 : 와 줄 들여쓰기를 사용한다. 위의 알골 예제 프로그램에는 아래와 같은 복합문 사용이 나온다.
if abs(a[p, q]) > y then
begin
y := abs(a[p, q]);
i := p; k := q
end
if 의 조건이 참이면 begin과 end 사이에 있는 3개의 대입문이 실행된다. 복합문이라는 명시적인 표현이 없다면 다음과 같이 작성해야 할 것이다.
if abs(a[p, q]) > y then go to l1;
goto l2;
l1:
y := abs(a[p, q]);
i := p; k := q
l2:
복합문을 사용하면 훨씬 이해하기 쉬운 코드가 됨을 엿볼 수 있다. 알골의 복합문은 내포된 블록nested block을 허용했다. 현대의 프로그래밍 언어는 대부분 내포된 블록을 지원한다.
알골의 복합문은 프로그램의 흐름control flow이라는 측면에서만 스코프scope 개념을 가졌다. 현대와 같이 변수의 life cycle이라는 측면에서의 스코프scope는 도입되지 않았다.
프로시저 (Call-by-value, Call-by-name)
프로그래밍 작업의 고단함은 코드의 재사용에 대한 강렬한 바람을 불러왔다. 그래서 첫 저장형 프로그램 컴퓨터의 사용자에서부터 서브루틴subroutine이라는 개념을 주장하고 나섰다.10 서브루틴이라는 표현은 함수function 혹은 프로시저procedure라는 식으로 표현되기도 했는데 코드의 재사용이라는 측면에서는 같지만 프로그래밍 언어에 따라서는 의미와 형태가 조금씩 다르다.
알골 58에서는 함수와 프로시저가 별개의 개념으로 제안되었다. 이는 포트란의 영향을 받았다고 보여진다. 포트란에서는 함수와 서브루틴, 두 가지 형태가 있었다. 함수는 수학의 함수처럼 입력값을 받아서 하나의 출력값을 토해내는 개념이었다. 이에 비해 서브루틴은 출력값을 만들지 않고 그 대신에 입력값을 수정하는 개념이었다. 알골 58의 함수와 프로시저도 이와 유사했다.
하지만 알골 60에 와서는 이를 통합해서 프로시저라는 형태 한 가지가 두 가지 스타일을 모두 감당하도록 만들었다. 이를 위해 Call by value와 Call by name이라는 개념이 도입되었다.
Call by value는 프로시저에게 전달하는 인자값(입력값)이 말 그대로 값 그 자체이다. 프로시저는 이 값으로 뭔가 계산을 해서 결과를 만들어낼 수 있지만 이 입력값 자체를 수정할 수는 없다. 전형적인 수학 함수의 모습이다. 하지만 Call by name은 인자값을 전달하는 것이 아니라 변수 자체를 전달한다. 그래서 프로시저 내에서 그 변수를 마음대로 건드릴 수가 있다.
integer i; integer array v[1:100];
for i:=1 step 1 until 100 do v[i]:=i;
procedure SUM(u);
integer u;
begin
integer s; s:=0;
for i:=1 step 1 until 100 do s:=s+u;
NSUM:= s
end
위의 코드를 보면 프로시저의 인자인 u에 value라는 별도의 선언이 없다. 따라서 이 인자는 call by name으로 동작한다. 이제 아래와 같은 코드가 수행된다고 하자.
i:=5;
SUM(v[i]);
그러면 프로시저의 u에 v[i]가 치환되어서 결국은 아래와 같은 의미의 코드가 수행된다.
for i:=1 step 1 until 100 do s:=s+v[i];
결과값은 1050이 된다.
만약 프로시저의 정의가 다음과 같다고 한다면 어떻게 될까?
procedure SUM(u);
value u; integer u;
begin
integer s; s:=0;
for i:=1 step 1 until 100 do s:=s+u;
VSUM:= s
end
여기서는 u가 value로 지정되어 있으므로 u 자리에 v[i]가 치환되는 것이 아니라 v[5]의 값인 5가 치환된다. 결국 아래와 같은 의미의 코드가 수행된다.
for i:=1 step 1 until 100 do s:=s+5;
결과값은 500이 된다.
Call by name은 서브루틴의 사용을 유연하게 만드는 효과가 있지만 미처 예상치 못한 부작용side effect 문제가 따라오기도 한다.
재귀적 호출과 스택
재귀적 호출recursion은 프로시저 내에서 자신을 호출하는 경우를 말한다. 재귀적 호출은 프로그램의 크기를 최적화시키고 경우에 따라서는 직관적으로 이해하기 쉬운 프로그램이 되도록 만들어 준다. 하지만 재귀적 호출을 구현하기 위해서는 메모리 공간을 제대로 관리해주어야 한다. 왜냐하면 동일한 명칭의 변수들이 다시 사용되기 때문이다.
PROCEDURE fact(n)
value n; integer n;
BEGIN
integer r=n;
if r > 1 then fact := fact(n-1) * n else fact := 1;
END
위의 예에서 fact(3)이라고 프로시저를 호출하게 된다면 프로시저 안에서 fact(2)를 호출하게 되고 다시 fact(2)를 수행하는 과정에서 fact(1)을 호출하게 된다. 문제는 fact라는 프로시저가 다시 호출될 때마다 r이라는 변수도 다시 사용된다는 점이다. 만약 fact(3)을 수행할 때 대입했던 r 변수의 값이 fact(2)를 수행할 때 덮어쓰여져 버리면 이 프로그램의 결과는 예상과 달라질 것이다. 따라서 프로시저가 다시 호출될 때는 내부 변수가 새롭게 할당되도록 만들 방법이 필요하다.
변수의 중복 사용만 문제가 아니다. 프로시저가 수행된 후 호출했던 곳으로 되돌아오기 위한 방법이 필요하다. 프로시저 호출이 내포nested되는 경우가 없다면, 즉 프로시저가 호출되면 다른 프로시저를 호출하는 일이 없이 복귀한다면 프로시저 호출이 완료된 후 되돌아올 곳의 주소를 특정한 곳에 저장해놓기만 하면 된다. 프로시저 수행이 종료되면 이 특정한 곳에 저장된 값을 읽어서 이 값을 이용해 원래의 자리로 돌아가면 된다. 하지만 만약 프로시저 내에서 다시 프로시저를 호출하게 된다면, 이 방법에서는 그 특정한 곳에 저장된 값이 덮어쓰여져 버려서 결국 돌아갈 곳의 주소를 잃어버리는 문제를 일으킨다. 따라서 돌아갈 주소의 저장 공간으로 특정한 곳을 사용하는 일은 바람직하지 않다.
스택stack은 위의 두 가지 문제를 모두 해결해준다. 컴퓨터 시스템에서 스택이란 메모리를 관리하는 방식 중 하나이다.11 스택 방식에서는 메모리를 연이어 할당한 후에 할당된 메모리를 연이어 해지할 때 할당된 순서의 역순으로 해지가 이루어진다. 알골에서 프로시저를 위한 메모리 할당이 이렇게 이루어진다. 프로시저가 호출될 때마다 메모리에서 이 프로시저를 위한 메모리 공간이 할당된다. 이 메모리 공간에는 프로시저가 사용하는 지역 변수를 위한 공간과 되돌아갈 주소를 저장하는 공간이 미리 정해진 규칙에 따라 만들어진다. 프로시저마다 새롭게 메모리가 할당되므로 재귀적 호출을 하더라도 지역 변수가 덮어쓰여질 위험이 없어지며, 되돌아갈 주소를 저장하는 공간도 항상 프로시저 호출때마다 별도로 만들어지므로 잃어버릴 위험이 없다.
알골은 스택을 제대로 다룬 언어이다. 스택이라는 방식이 고안되지 않았다면 현대의 각종 프로그래밍 기법은 존재하기 어려웠을 것이다.
알골이 현대 프로그래밍 언어의 기반이 된 이유는 여러가지가 있을 수 있다. 여기서는 두 가지를 언급해보고자 한다. 첫 번째는 전 세계의 전문가들이 의견을 모아 만들었다는 점이다. 당시 컴퓨터를 사용할 수 있는 사람들이 극히 한정적이었으므로 사실상 전 세계의 전문가들이 모두 참여했다고 보아도 무방하지 않을까 싶다. 두 번째는 실제로 만들어 본 전문가가 주도적으로 참여했다는 것이다. 흔히 프로그래밍 언어의 4대 조상을 들라면, 포트란, 코볼, 리스프, 알골을 꼽는다. 그런데 포트란을 만든 존 백커스와 리스프를 만든 존 매카시가 모두 알골 설계의 핵심 인물이었다. 따라서 알골은 이상과 현실을 모두 담을 수 있었다고 볼 수 있지 않을까?
알골 언어로 프로그래밍을 실제로 작성해본 사람은 많지 않을 것이다. 알골 컴파일러가 제대로 대중화되지 않았기 때문이다. 그렇지만 프로그래밍 언어를 배워본 사람이라면 왠지 알골 언어가 익숙하게 느껴지는 이유는 무엇일까? 그것은 논문이나 서적에서 알고리듬 혹은 예제 프로그램을 표현할 때 알골 언어를 많이 사용했기 때문이다. 비록 실제 프로그램을 만드는 용도로는 쓰이지 못했지만 아이디어를 표현하는 용도로는 훌륭히 제 역할을 했다. 현대 컴퓨터의 발전은 알골에게 큰 공을 돌려야 마땅하다.
답글 남기기