왜 Forth가 그렇게 대단한가?

차례
🖋
편집자 주 Forth에 대해 관심을 가지게 된 결정적인 계기의 글. 1987년에 쓰여진 글을 90년대에 번역했기 때문에 전반적으로 컴퓨터 환경이 지금과는 비교할 수 없이 열익했다. 1바이트, 1싸이클리 중요하던 시대였고, 여러 컴퓨터 언어가 혼재하다 보니 '나 이런 거 잘해. 너는 못하지?' 식의 도발이 여기저기서 이루어지던 시기였다. (살짝 중2병 스럽기도...) 최대한 고원용 님의 원문을 보존하려고 했으나 글의 맥락과 편집 등의 여건이 맞지 않아 수정된 부분이 있다. 특히 제목 체계가 정돈되지 않아 찾아보기에 어려움이 따른다. 그래서 원문에 없는 제목을 달 때에는 지금 문단처럼 본문과 다른 형식을 사용하였다.
 
포스의 장점을 설명한 프로그래머의 글이다. 부디 이 글을 읽고 포스를 쓰지 않는 프로그래머들에게 (프로그래머에게 포스를 써 보도록 영향을 미칠 수 있는 사람에게는 누구나) 이 글을 권했으면 좋겠다. 그리고 이글을 읽고 난 당신의 생각과 당신이 이 글에서 찾은 잘못을 반드시 내게 알려 주기 바란다. Roy Brunjes 71310,1427
 

Why is FORTH so great? (왜 포스가 그렇게 대단한가?)

 
내가 지금까지 접해 본, 흔히 쓰이는 프로그램 언어 중에 포스(Forth)만큼 강력하고 내맘대로 바꿀 수 있는 언어는 없다. 어떻게 내가 이렇게 건방지고 일방적인 선언을 할 수 있느냐고? 나는 내 경험이 그렇다고 이야기하는 것이다. 당신은 아마 내가 다른 언어로는 프로그램을 하나도 짜 본 적이 없고 따라서 위 말은 귀담아 들을 필요가 없다고 생각할지 모른다. 그렇지 않다.
나는 펜실베니아 주립대학 전산학과를 졸업했다. 그 후 6 년 동안 취직해 있는 동안 많고 많은 프로그램 언어를 보았고 여러 가지 컴퓨터 언어로 프로그램을 짰다. 그 언어들에 대한 내 생각은 아래와 같다. 흘낏 보고 당신은 이렇게 말할지 모른다: "이 사람은 프로그램 언어 X를 써 본 적이 없고 X는 이렇게 좋은 점들을 많이 가지고 있기 때문에 이 사람의 말은 귀담아 들을 가치가 없다." 거기에 대한 내 대답은 이렇다.
프로그래머가 문법, 번역기(compiler)를 비롯하여 연산자와 자료형을 포함한 모든 것을 바꾸고 늘릴 수 있는 언어는 포스밖에 없다. 다른 모든 언어는 작든 크든 간단하든 복잡하든 번역기가 고정되어 있다. 오직 번역기를 만든 사람만이 번역기의 내부 작용과 언어를 바꿀 수 있다. 처음 나온 번역기에 어떤 기능이 없어서 번역기 X의 다음 버전을 기다린 적이 있는 사람이 하나둘이 아닐 것이다. 포스에서는 그런 일이 없다. 필요할 때 필요한 기능을 언제든지 당신이 포스에 더할 수 있다.

언어 별 특징

포트란(FORTRAN)

수를 다루는데는 좋지만 그밖에는 없는 것이 너무 많다. 많은 사람들이 여러 번 개선을 했지만 사용자 정의 자료형도 없고 내가 쓰기에는 부족하다.

PL/I

한동안은 계산 기능과 사무용 자료 처리 기능이 비교적 균형있게 갖춰진 언어라고 생각했었다. 그래도 사용자 정의 자료형을 쓰기에는 불편하다. 나쁜 언어라고 할 수는 없지만 계속 읽다 보면 왜 포스가 훨씬 나은지 보게 될 것이다.

파스칼(Pascal)

자료형을 쉽게 만들 수 있고 만든 프로그램을 읽을 수 있어서 오랫 동안 가장 즐겨 쓰던 언어였다. 시간이 지나자 자료형을 까다롭게 따지는 것이 오히려 거치적거리기 시작했다. 파스칼에서는 자료형을 만들고 사용할 수 있지만 번역할 때 모든 이름과 연산의 자료형이 결정되어야 한다. 그래서 자료형이 까다롭다고 (strong typing) 하는 것이다.
까다로운 자료형의 장점은 자료형을 잘못 사용할 가능성이 작다는 것이다. (예를 들어 두 문자를 더하기는 매우 어렵다.) 대부분의 경우 그런 시도는 프로그래머의 실수이다. 하지만 프로그램을 짜다 보니 필요한 때도 있었는데 파스칼은 기를 쓰고 그걸 막았다. 가변 레코드(variant record)를 써서 돌아갈 수 있지만 매우 불편하고 비효율적 이다. 그나마 포인터(pointer)로 덧셈 뺄셈을 하려면 이 방법도 소용이 없다. 포인터를 쓰는 것도 완전하지 않다.

APL

APL은 매우 강력한 언어이다. 내가 본 언어 중 구문이 가장 간결하다. 문제는 원시 코드(source code)가 너무 간결하다는 것이다. 한두 줄 짜리 프로그램도 쓴지 한달이 지나면 이해하기가 쉽지 않다. APL에 나름대로의 장점이 있지만 범용 프로그램 언어로는 부적당하다.

C

C는 꽤 강력한 언어이다. C가 강력한 것은 포인터를 조작하는 기능을 모두 갖추었고 파스칼보다 자료형이 덜 까다롭기 때문이다. C가 다른 언어에 비해 옮기기 쉽지만 (portable) C 번역기를 파는 회사들이 주장하는 것만큼 쉽지는 않다. 옮길 수 있는 프로그램을 만드는 것은 프로그래머이지 언어 자체는 아니라는 것이 내 믿음이다. 내가 머리 속에서 계속 생각하고 있지 않으면 프로그램은 금방 옮길 수 없게 자라 버린다. 물론 마음먹으면 옮길 수 있는 C 프로그램을 쓰는 것도 똑같이 쉽다. 이것은 C뿐 아니라 거의 모든 프로그램 언어에 대해서 참이다. 프로그래머가 언어의 기능을 조심스럽게 골라 쓰면 옮길 수 있는 프로그램을 만들 수 있다. 물론 이렇게 하려면 특정한 번역기에만 있는 기능을 쓰면 안된다. 만들어진 프로그램은 아마도 덜 반짝거리겠지만 옮길 수 있게 만들려면 어쩔 수 없다. 프로그래머들이 C를 많이 쓰는 것을 보면 C가 아주 넓은 범위의 문제를 해결하는데 쓸모가 있는 것은 분명하다.

어셈블리어(Assembly Language)

어셈블리어(
어셈블리
어셈블리
)에 대해 어떻게 생각하는가? 어떤 컴퓨터에 대해서도 이것이 가장 강력하고, 유연하고, 생각할 수 있는 좋은 말은 모두 갖다 붙인 프로그램 언어일 밖에 없다. 모든 언어는 결국 기계어로 바뀌기 때문에 기계어에 1:1로 대응하는 어셈블리어로 안되는 일이라면 어떤 언어를 써도 컴퓨터는 그 일을 할 수 없다. 다시 말해 어셈블리어로 안된다면 안되는 것이다.
그러나 어셈블리어의 장점은 동시에 단점이다. 기계의 모든 면을 조작할 수 있다는 것은 매우 강력하지만 언제나 그래야 한다면 너무 번거롭다. 필요할 때는 모든 면을 조작하고 필요치 않을 때는 그렇게 하지 않아도 된다면 이상적이겠다.
매우 효율적이라는 것은 어셈블리어의 장점이다. 단 한 순간도 낭비하지 않는 응용 프로그램을 만들 수도 있다. 사람들이 쓰기 어렵다고 생각하는 것은 다른 단점이다. 나는 어렵다고 생각하지 않는데 많은 사람들은 그렇다고 말한다. 또 다른 단점은 프로세서가 다 다르기 때문에 한 프로세서에 쓰려고 짠 프로그램을 다른 프로세서에 쓰려면 완전히 새로 짜야 한다는 것이다. 이것은 또 프로세서가 바뀌면 프로그래머가 새로 공부를 해야 한다는 뜻이기도 하다. 공부하는 데는 시간이 걸리고 처음에는 한 프로세서의 도사가 다른 프로세서에서는 덧셈도 할 줄 모르는 완전한 초보자이다.

코볼(COBOL)

나는 코볼로 프로그램 짜는 것을 배우지 않았다. 많은 사람들은 나보고 그래서 머리 상하는 걸 면했다고, 과거로 돌아갈 필요가 없다고 말한다. 이것은 사무처리 분야에서 지금도 한창 날리고 있는 언어한테 너무 심한 말인지도 모른다. 전 세계에서 오늘도 수 십억 줄의 코볼 코드가 실행되는 걸로 보아서 코볼은 쓸모있는 언어인 것 같다. 하지만 내가 만나본 전문적인 코볼 프로그래머들은 코볼을 써서 문제를 해결할 수는 있지만 언어 때문에 자주 짜증이 난다고 말했다. 비록 문제를 해결할 수는 하지만 코볼 프로그래머들은 코볼을 좋아하지 않는다.

Ada

Ada는 아주 많은 기능을 가진 언어이다. Ada는 큰 무른모(software) 시스템을 개발 유지하기 위해 만들어졌다. 미국 국방성이 언어의 사양을 결정하고 국방성의 모든 프로그램을 Ada로 쓸 것을 요구했다. 당연히 Ada는 단번에 인기있는 언어가 되어 엄청난 양의 프로그램이 Ada로 작성되고 (다른 언어로 쓰였던 프로그램이) 재작성되었다. 그렇다고 해서 좋은 언어일까? 반드시 그렇다고는 말할 수 없다. 개선된 기능이 많이 있지만 내가 느끼기에 Ada는 너무 크다. 5 년 동안 날마다 Ada로 일을 하고도 Ada의 기능을 다 알지 못할 수도 있다. 마치 태평양 한 가운데 구명보트를 타고 있는 것 같이 느낄지 모른다. Ada는 파스칼의 단점을 보완했기 때문에 시스템은 칸칸이 잘 나뉘어서 유지하기 쉽다. 그렇지만 기능이 너무 많아서 프로그래머가 헤메기 쉽다. PL/I 프로그래머 중에 이렇게 느끼는 사람들이 있었지만 Ada의 경우 이것은 더욱 두드러진다. 작든 크든, 나라에서 하는 일치고 효율적인 게 있던가? 그리고 Ada에서도 번역기를 고칠 수 없다.

모듈라 2 (Modula 2)

모듈라 2는 Nichlaus Wirth가 자신이 만든 파스칼을 고쳐 쓴 것이다. Wirth는 전산학계에서 아주 존경받는 인물이고 그의 업적은 크고 많다. 다른 사람들이 파스칼에 대해 불평한 것을 고친 것이 모듈라 2이다. 어느 정도는 성공했다. 따로따로 번역했다가 합칠 수도 있고 필요하면 까다로운 자료형을 피하는 방법도 있다. 겉으로 보기에 파스칼보다 프로그램 짜기가 편할 것 같다. 책을 몇 권 읽었을 뿐 직접 모듈라 2로 프로그램을 짠 적이 없기 때문에 나는 모듈라 2의 장점도 단점도 말할 처지에 있지 않다. 단점을 지적하자면 이것도 위에 언급한 다른 언어들처럼 고정되어 있다는 것이다. 새 연산자를 더할 수도 없고 이미 정해진 기능을 바꿀 수도 없다.

포스(Forth)

왜 포스냐고? 앞의 언어들 모두가 상당한 공을 들여 만든 것인데 포스가 이것들보다 정말 나으냐고? 먼저 한 마디 해 두겠다. 다른 모든 언어들처럼 포스도 장점과 단점이 있다. 세상에 완전한 것은 없고 포스도 예외는 아니다. 내가 포스의 장점이라고 생각하는 것을 이야기하고 포스의 단점 내지는 약점이라고 생각하는 것을 이야기하겠다. 그리고 포스의 좋은 점을 몇 가지 더 이야기하고 이 글을 끝내겠다.

포스의 특징: 확장성

포스의 특징

먼저 포스는 범용 프로그램 언어이다. 일상적인 문제를 해결하는데도 쓸 수 있고 특별한 굳은모(hardware)와 무른모를 써야 하는 특별한 응용에도 쓸 수 있다. '확장할 수 있다'는 것이 포스의 첫번째 특징이다. 프로그래머는 새 말을 자꾸자꾸 더해서 더욱 더 높은 단계로 프로그램을 짜 나간다.
그게 뭐 대단한 거냐고? 대부분의 현대적인 언어에서 이런 식으로 프로그램을 짤 수 있다고 당신은 생각할 것이다. 파스칼에서는 기존의 절차(procedure)와 함수로 새 절차와 새 함수를 만들고 이들을 차례차례 쌓아서 아주 높은 단계의 구조를 만들 수 있다.
C에서도 함수 라이브러리로 언어를 훨씬 더 쓸모있게 만들 수 있다. 실제로 얼마나 라이브러리가 잘 갖추어졌느냐가 C 번역기의 쓸모를 결정한다 해도 과언이 아니다. 이런 식의 '벽돌 쌓기' 프로그램 방법은 거의 모든 언어에 적용된다. 따라서 포스의 '확장성'이란 다른 모든 언어가 가진 특색--어떤 절차/함수가 다른 절차/함수를 부를 수 있는 기능--을 거창하게 포장한 말이다, 그렇지?
반은 맞고 반은 틀렸다. 지금부터 나는 포스를 C와 비교하겠다. 다른 언어들과 비교하는 것은 당신 몫으로 남겨 두겠다. C와 비교하는 첫번째 이유는 C가 매우 인기있는 언어이고 두번째 이유는 많은 사람들이 C가 고급어 중 가장 말랑말랑한 (flexible) 언어라고 믿기 때문이다.

포스 vs C

이제 C에서 함수 라이브러리를 쓰는 능력과 포스의 확장성이 어떻게 다른가 보자. C로 프로그램을 짜다 보니 printf 함수가 수를 출력하는 방식이 마음에 들지 않는다고 가정하자. 마음에 들게 printf를 새로 쓰면 되니 큰 문제는 아닌 것 같다. 그런데 새로 쓰려는 printf의 대부분의 기능은 번역기의 printf에도 있는 것이다. 문자열을 다루는 방식은 특히 그렇다. 바꾸고 싶은 것은 수를 출력하는 방식이다.
그러나, printf의 원시 코드가 없기 때문에 바닥에서 출발해서 완전히 다시 짜야 한다. 상업적인 번역기의 라이브러리에 있는 함수들은 매우 공을 들여 다듬어진 것이기 때문에, 새로 만든 printf가 원하는 기능을 제공하기는 해도 너무 크거나 너무 느릴지 모른다. 그러면 별 수 없이 시간을 들여 새로 만든 함수를 다듬어야 한다. 참으로 큰 일이다.
포스에는 함수도 없고 파스칼에서의 절차도 없다. 포스에서는 C의 함수를 낱말(word)이라고 부른다. 포스는 자기가 '아는' (코드를 가진) 모든 '낱말'을 사전에 보관한다. 파스칼에서는 절차나 함수가 감싸일(nesting) 수 있어서 속의 함수/절차를 부르려면 그것을 싸고 있는 함수/절차를 불러야 하지만 C에서는 모든 함수가 한 층에 있다. 대부분의 사람들은 이렇게 한 층에 모든 게 있는 것을 좋아한다. 포스에서 모든 낱말은 사전에 들어 있고 C에서처럼 모두가 같은 층에 있다. 여기까지는 C와 포스가 동점이다.

첫 대면: "." 고쳐 쓰기

앞의 예로 돌아가자. 비록 C 프로그래머가 printf를 다시 짤 수 있지만 어렵고 힘이 든다. 포스에는 문자열을 출력하는 낱말도 여러 개 있고 수를 출력하는 낱말도 여러 개 있다. 수를 출력하는 가장 기본적인 낱말은 따옴표를 뺀 "."이다. 이것은 "점"(dot)이라고 읽는다. 이제 점이 수를 출력하는 게 마음에 들지 않는다고 가정해 보자.
포스의 점은, 더미(스택, stack)의 맨 위에 있던 수를 지금 커서 위치에 왼쪽을 맞추어 출력하고 빈칸을 하나 출력한다. 수가 0보다 작을 때만 음수 부호가 수 앞에 출력된다.
한 예로 더미의 맨 위에 있는 수가 0보다 클 때만 그 수를 출력하고 0보다 작으면 잘못되었다는 표시로 -999를 더미의 맨 위에 남기도록 점을 고쳐 쓰고 싶다고 해보자. 이렇게 "점"을 고쳐 쓰는 것이 특별히 쓸모가 있을 것 같지는 않지만 예로는 충분하다.
포스에서 있는 낱말을 고쳐 지을 때 같은 이름의 '옛' 낱말을 써서 새 낱말을 정의 할 수 있다. 헷갈리는 말 같지만 그런게 아니고 아주 쓸모있다. 아래에 고쳐 쓴 점의 코드를 보이고 설명을 하겠다.
 
: . ( n -- | n -- -999 ) DUP 0> IF . ELSE DROP -999 THEN ;
: . ( n -- | n -- -999 ) 베껴 0> 면 . 아니면 버려 -999 라 ;
(옮긴이: THEN을 왜 "라"로 바꾸었는지 얼른 안 들어올지 모르지만 -999를 무시하면 "버려 라"가 우리말로 자연스럽다.)
 
이것을 보고 당신은 배꼽을 잡고 웃거나 이 글을 그만 읽으려 할지 모르겠다. 그러나 계속 읽어라. 포스의 특색을 보이기 위해 일부러 이 예를 골랐다.
위 예를 차례로 설명하겠다.
 
  1. : ("쌍점(colon)"이라고 읽는다.) ":"는 (믿거나 말거나) 번역기이다. 이것이 번역기의 실행을 시작한다.
  1. . ("점(dot)"이라고 읽는다.) "."은 우리가 정의하려는 '새' 낱말의 이름이다. 내가 앞에서 말했듯이 이 낱말은 포스의 일부이다. 같은 이름의 낱말이 두 개가 있어도 포스는 아무 불평을 하지 않고 가장 나중 정의를 사용한다.
  1. ( n -- | n -- -999 ) 이것은 덧붙임말이다. 포스 번역기는 괄호 안에 있는 것을 덧붙임말로 간주한다. 왜 이렇게 이상한 덧붙임말을 넣느냐고? 이것은 더미 효과 덧붙임말이라고 불리는 특별한 종류로 이 낱말이 더미에서 가져올 값과 (이 경우에는 n이라고 써서 수라는 것을 표시했다) 이 낱말이 실행된 후에 더미에 남기는 값을 표시한다. 이 경우 새 "점"은 아무 것도 남기지 않거나 잘못을 표시하는 -999를 남긴다. 번역기는 이 더미 효과 덧붙임말을 무시하지만 프로그래머는 이것을 보고 이 낱말에 무엇을 입력해야 하는지 알 수 있다.
  1. DUP (베껴) DUPlicate를 줄인 이 낱말은 더미의 맨 위 값을 베껴서 포개 놓는다. 실행되고 나면 더미에는 같은 값이 두 개 남는다. 더미에 밑에 서부터 차례로 41, 16, 67이 있었다면 DUP를 실행하고 난 후에는 41, 16, 67, 67이 남는다.
  1. 0> 이 낱말은 더미의 맨 위 값과 0을 비교해서 크면 '참'값을 남기고 아니면 '거짓'값을 더미의 맨 위에 남긴다. (옮긴이: 피그미 포스가 0>를 모른다고 투덜거린다면 ": 0> 0 > ;"로 0>를 정의한다.)
  1. IF () 이 낱말로 IF문이 시작된다. 더미의 맨 위 값이 '참'이면 (0이 아니면) IFELSE 사이에 있는 코드가 실행되고 프로그램의 흐름은 ELSE로부터 THEN 다음으로 건너 뛴다. 더미의 맨 위 값이 '거짓'이면 프로그램의 흐름은 ELSETHEN 사이에 있는 코드로 건너 뛴다. 어느 경우든 프로그램의 흐름은 THEN 뒤로 이어진다. 다른 언어에서처럼 ELSE문은 선택적이다. 있을 수도 있고 없을 수도 있다. 다른 언어에서와 반대라고? 두 가지 해결책이 있다.
      • 결국 익숙해지거나
      • 마음에 들지 않는다면 바꾸면 된다! 이것이 바로 포스의 장점이다! 곧 이것에 대해 설명하겠다.
  1. . 이것은 '옛' 점이다. 마음에 들지 않아 고치려 하지만 점을 새로 정의하는데 이 낱말을 쓸 수 있다. 이것은 아주 쓸모 있는 특색으로 포스가 사전을 사용하는 방법에서 비롯된 것이다. (그러니까 overriding 한다는 뜻)
  1. ELSE(아니면) IF ... ELSE ... THEN 구조는 6 번에서 설명했다.
  1. DROP (버려) 이 낱말은 더미의 맨 위 값을 없앤다.
  1. -999 이것은 수이고 이 값이 더미의 맨 위에 얹힌다.
  1. THEN () IF ... ELSE ... THEN 구조는 6 번에서 설명했다.
  1. ; ("세미콜론(semicolon)"이라고 읽는다) 이 '낱말'로 모든 ":" 정의를 끝낸다. 이 낱말은 번역기에게 지금 번역하고 있는 낱말의 정의가 끝났다고 알린다.
 
당신이 지금 무슨 생각을 하고 있는지 나는 짐작할 수 있다. 이 이유로 많은 사람들이 과거에 포스에 대해 나쁘게 말했었다. "도대체 읽을 수가 없지 않은가?" 서로 닮은 다른 프로그램 언어들을 배운 뒤에 포스를 처음 접했을 때의 내 반응도 이랬었다. 머리에 처음 떠오른 생각은 "딴세상" 거라는 것이었다. 당신을 진정시키려고 몇 마디 하겠다.
 
  1. 익숙하지 않기 때문에 이상하게 보인다.
  1. 포스에서는 언제나 이름을 바꿀 수 있다. "." 대신에 PRINTPRINT.NUMBERPRINT_NUMBER라고 하든 printfWRITE라고 하든 빈칸을 포함하지 않은 문자열이면 당신 마음대로이다. (옮긴이: 이때문에 포스에서 우리말 이름으로 낱말을 짓는데 아무 문제가 없다.)
    1. 빈칸을 (ASCII 32) 포함하지 않은 어떤 문자열도 포스 낱말로 쓸 수 있다. 포스는 빈칸으로 낱말과 낱말 사이를 구분한다. 따라서 *&^abdger`~도 포스 낱말로 쓸 수 있다. (하지만 당신이 이런 이름을 좋아한다면 병원에 가보는 게 좋겠다.) 나한테는 CAVE보다 (CAVE는 Compute AVErage를 줄인 말이다) COMPUTE.AVERAGE가 좋지만 포스는 상관하지 않는다. 포스 프로그램을 읽을 수 없다고 불평하지만 실제로는 프로그래머가 원하는 만큼 읽기 쉽게 쓸 수 있다. 이것은 C에서도 마찬가지다. 어떤 함수를 cave라고 부를 수도 있겠지만 나는 comp_avg나 이와 비슷한 이름을 쓴다. 프로그램을 읽기 쉽거나 어렵게 만드는 것은 프로그래머이지 언어가 아니다. 실제로는 뜻이 통하는 이름을 짓는 데 포스가 덜 거치적거린다. 잘만 쓴다면 프로그램 언어는 자유로울 수록 좋다.
  1. "더미"(stack)에 대해 뭘 그렇게 자주 이야기하냐고? 포스에는 자주 쓰는 스택이 최소한 두 개가 있다. 프로그래머는 필요하다고 생각하는 만큼 스택을 늘릴 수 있다. 대부분의 경우 두 개면 충분하다. 가장 자주 쓰는 스택은 포스 자료 더미(data stack)이다. 이것은 컴퓨터 용어에서 전통적인 의미의 스택이다. 즉, 나중 들고 먼저 나감 (last in first out, LIFO) 자료 구조이다. 자료 더미는 포스의 거의 모든 낱말이 사용하는 자료를 보관한다. "더미의 맨 위 값"을 입력으로 가져 온다고 말할 때는 이 자료 더미를 말한다. 자료 더미는 유한하지만 컴퓨터의 메모리를 모두 차지할 만큼 커질 수도 있다. 하지만 순환문에서 잘못하여 더미에 값을 계속 올려 놓지 않는다면 보통의 경우 256 바이트로 충분하다.
    1. 다른 더미는 되돌이 더미(return stack)이다. 이것은 한 낱말이 다른 낱말을 부를 때 돌아올 주소를 보관한다. A 낱말이 B 낱말을 부를 때 돌아올 A 낱말 안의 주소가 되돌이 더미에 보관된다. B 낱말이 끝났을 때, 되돌이 더미에서 A의 주소가 꺼내진다. 포스의 장점 중의 하나는 되돌이 더미를 자료를 임시로 보관하는데 쓸 수도 있고 직접 되돌아 올 주소를 바꿀 수도 있다는 것이다. A 낱말이 B 낱말을 부르고 이것이 다시 C 낱말을 부를 경우 프로그래머가 원한다면 B와 A로 돌아가는 되돌이 주소를 모두 버리고 C 낱말이 운영 체제로 바로 돌아 올 수도 있다. 부른 곳으로 돌아가지 않는 것이 좋은 경우는 생각보다 잦다. 저 아래 수준에서 에러가 발생하면 부른 곳으로 돌아가는 것이 전혀 의미가 없을 수 있다. 아니면 A가 B를 부르고 B가 C를 불렀을 경우 C에서 에러가 발생해서 A에게 알리는 것이 필요할 수도 있다. 이 경우 B에 에러를 검사하는 코드를 잔뜩 집어 넣는 (이 조건이 발생하면 이렇게 하고 아니면 저렇게 하고, 저 조건이 발생하면 ...) 대신 B로 돌아가는 되돌이 주소를 없애 버리고 에러가 발생했음을 알리는 값을 자료 더미의 맨 위에 놓고 바로 프로그램의 흐름을 A로 가게 할 수 있다.
      더미를 마음대로 조작하는 것에서 보듯 포스를 쓰면 프로그램으로 조종하려는 기계를 완전히 제어할 수 있다. 그리고 기계를 이렇게 완전히 제어할 수 있기 때문에 포스는 매우 강력하다.
      포스는 더미와 뒤붙임 표기법(postfix notation, reverse Polish notation, RPN)을 사용한다. 익숙해지면 뒤붙임 표기법은 '보통' 표기법보다 하나도 어렵지 않다. 보통 사람이 하루면 뒤붙임 표기법에 익숙해질 수 있다. 뒤붙임 표기법을 아래에 설명하겠다. (실제로 하루면 충분하다. 더구나 우리 말 어순이랑 묘하게 닮아있다.)
      +, -, *, / 등의 모든 연산은 연산에 필요한 수를 더미의 위에서 꺼낸다. 덧셈을 하려면 두 수가 필요하기 때문에 + 연산자는 더미에서 두 수를 꺼내 더하고 그 결과를 더미의 맨 위에 얹는다.
      따라서, 익숙한 표현인 "3 + 4"는 뒤붙임 표기법으로는 "3 4 +"가 된다. (옮긴이: 이는 우리말 순서와 같다. "3과 4를 더하라.") 연산되는 수가 연산자 앞에 놓인다. + 연산자는 (더미의 맨 위에 있는) 4를 먼저 꺼내고 (이제 더미의 맨 위에 놓인) 3을 꺼내서 두 값을 더하고 결과를 더미의 맨 위에 놓는다. 다시 말하지만 수를 먼저 입력하고 나서 연산자를 입력한다. "3 2 +"는 더미에 5를 남긴다.
      이 자리에서 뒤붙임 표기법에 관해 더 자세히 설명하지는 않겠다. 뒤붙임 표기법을 전혀 본 적이 없는 사람이 이것에 아주 편안해지는 데 최대로 하루가 걸린다는 것만 말하고 지나가기로 한다.

말랑말랑한 포스

번역기(compiler) 고쳐서 쓰기

포스에서 되돌이 더미의 내용을 바꿀 수 있다고 말한데 대해, C나 다른 언어를 써서도 한 함수(낱말)가 어떤 함수로 돌아갈 지 결정할 수 있다고 반박할 지 모른다. 그러나 포스에서는 애시당초 부른 적이 없는 낱말로도 프로그램의 흐름을 옮길 수 있다. 보통의 경우라면 이렇게 하는 것이 별 소용이 없을 뿐 아니라 위험하지만 필요하다면 할 수 있다. 포스를 발명한 찰스 무어(Charles Moore)는, 프로그래머가 시스템의 어떤 기능이라도 더하거나 빼거나 바꿀 수 없다면 어떤 언어도 모든 사람에게 모든 해답을 줄 수 없다는 것을 일찌기 알아차렸다. 그래서 그는 모든 것을 바꿀 수 있는 언어를 만들었다. 어떤 것도 바꿀 수 있고 더할 수 있고 없앨 수 있다. 심지어는 번역기도 바꿀 수 있다!
도대체 어떤 희한한 경우에 번역기를 바꾸려고 한단 말인가? 당연히 이런 의문을 품을 것이다. 그러나 이렇게 하는 것이 편리한 경우가 나한테 몇 번 있었고 틀림없이 다른 사람한테도 있었으리라고 생각한다. 번역기를 바꿀 수 있다면 그렇게 해서 짠 프로그램을 어떻게 다른 기계에서 실행시킬지 당연히 의문이 생길 것이다. 번역기를 바꿔서 짠 프로그래머 갑의 포스 프로그램은 전혀 포스처럼 보이지 않을 수도 있다. 프로그래머 을이 갑의 시스템에서 어떤 코드를 옮겨 오고 싶지만 을의 번역기로는 번역을 할 수 없을지도 모른다. 이 경우 을은 어떻게 해야겠는가? 을은 갑에게 어떻게 바꾸었는지를 물어서 을이 이미 고친 번역기에 갑이 한 모든 수정을 더하면 된다. 식은죽먹기다. C 프로그래머에게도 비슷한 상황이 생길 수 있다. 내가 C 프로그래머이고 당신이 짠 3차원 숨은 선 제거 함수를 쓰고 싶은데 당신이 그 함수를 짤 때 사용한 라이브러리가 없다면 그 라이브러리를 내가 구하기 전까지 나는 당신의 함수를 사용할 수 없다.
그래서 C와 포스는 마찬가지다, 맞지? 틀렸다. C에서는 번역기나 연산자나 다른 많은 것들을 당신이 원하는 대로 행동하게 바꿀 수 없다. 그래서 어쨌다는 거냐고 당신은 물을 것이다. 아래에 번역기를 바꾸는 것이 편리했던 내 경우를 예로 들겠다.

윤곽 잡개(profiler) 만들기

나는 프로그램 실행의 윤곽을 잡는 프로그램을 (profiler) 짜고 싶었다. 이 윤곽잡개 프로그램 A는 다른 프로그램 B가 실행되는 것을 '지켜 보고' 있다가 B가 끝난 후 표시한 낱말(함수)이 몇 번이나 불려졌는지 각각의 경우 실행시간이 몇 초였는지를 출력한다. 이 수치를 보고 코드의 어떤 부분을 빠르게 해야 할 지 알 수 있다. 물론 원시 코드를 보고 알 수 있는 경우도 있다. 루프나 특히 감싸인 루프는 빤히 그런 곳이다. 하지만 어떤 경우는 원시코드를 보기만 하는 것으로는 병목이 어딘지 알 수 없다.
이런 프로그램을 번역기 회사에서 제공하지 않는다면 다른 언어에서 당신은 상당한 노력을 들여야 만들 수 있을 것이다. 문제는 C에서 함수가 불릴 때마다 당신의 윤곽잡개 코드가 실행되어야 한다는 것이다. 필요한 코드를 당신 손으로 쓰는 것은 별 일이 아니지만 이렇게 하려면 그 코드를 모든 함수에 일일이 포함시켜야 한다. 내가 하고 싶은 것은 윤곽잡개 프로그램을 쓰지 않을 때와 똑같은 B의 원시 코드를 써서 "윤곽잡개 켜, 프로그램 돌려, 결과 보여"라고 시키려는 것이다. 이제 내가 무슨 소리를 하는 건지 알았을 테니 번역기를 바꾸는 것이 왜 필요한지 보자.
위에서 말한대로 포스에서는 낱말 :로 번역기를 부른다. 명령 통역기(command interpreter)가 :를 보면 번역기를 부른다. (명령 통역기는 보통 줄여서 통역기라고만 부른다.) 나는 낱말 :를 바꿔서 아래처럼 행동하게 만들었다.
번역할 때 : 번역기는 불릴 때마다
 
  1. 이 낱말이 불린 횟수, 낱말 실행이 시작한 시각과 끝난 시각, 총 실행 시간을 기록할 자리를 마련한다.
  1. 실행이 끝난 시각을 기록하고 이 낱말이 실행되는데 걸린 시간을 계산해서 이를 총 실행 시간에 더하는 코드를 낱말에 넣고 번역을 끝낸다.
  1. 앞에서 .을 고칠 때 '옛' .을 쓴 것과 마찬가지로, 원래 번역기를 써서 보통 때처럼 낱말을 번역한다. 포스에서는 이렇게 원래 번역기의 원시 코드가 없어도 낱말을 이용할 수 있다.
  1. 실행 회수를 하나 증가시키고 실행 시작 시각을 기록할 코드를 낱말에 넣는다.
실행할 때 이 낱말이 불려지면 번역기가 넣어둔 코드가 다음의 일을 한다.
  1. 낱말의 실행 회수를 하나 증가시킨다.
  1. 현 시각을 시작 시각 자리에 기록한다.
  1. 그 낱말의 원래 코드를 실행한다.
  1. 현 시각을 종료 시각 자리에 기록한다.
  1. 지금까지 이 낱말을 실행시키는데 걸린 총 시간을 계산하고 기록한다.
 
: 번역기를 바꾸는 것만으로 위의 모든 일을 할 수 있다. 다시 강조하지만 윤곽잡개 프로그램이 지켜보는 프로그램의 원시코드는 단 한자도 바꿀 필요가 없다. (위의 예에서 프로그램 A가 지켜보게 하기 위해 프로그램 B는 다른 일을 하나도 할 필요가 없다.) 포스는 낱말이 번역될 때의 행동도 실행될 때의 행동도 바꿀 수 있다. 이것은 아주 강력하다. 이것은 C 전처리기(preprocessor)가 매크로를 정의해서 사용하는 것과 닮은 점이 있지만 포스를 써서 할 수 있는 일은 그보다 훨씬 더 많다.
윤곽잡개 프로그램의 전체 원시코드는 겨우 4065 바이트이다. 번역할 때 이 프로그램의 사용법을 화면에 표시하는 코드를 포함하고도 그렇다. 프로그램이 번역될 때 사용법을 화면에 표시하는 것이 아주 편리하기 때문에 나는 보통 그렇게 한다. 이렇게 하는 것은 아주 쉽다. 사용법을 화면에 표시하는 코드를 낱말의 정의 밖에 놓으면--":"와 ";"의 바깥에 놓으면-- 통역기가 번역할 때 이 코드를 실행시킨다.
원시코드를 번역한 실행 코드는 1057 바이트이다. 어떤 언어로 이렇게 짧은 윤곽잡개 프로그램을 짤 수 있겠는가? 내가 알기로 포스말고는 없다. 실은 나중에 설명할 이유 때문에 번역한 포스 코드는 어셈블리어로 짠 프로그램을 어셈블한 것보다 더 짧다. 내가 이 말을 처음 들었을 때는 말도 되지 않는 소리라고 생각했었다. 하지만 지금까지 만들어진 거의 모든 포스에 대해 이것은 맞는 말이다.
참으로 놀라운 것은 포스 언어를 확장하는 것이 이렇게 쉽다는 것이다. 물론 C 프로그래머도 내 윤곽잡개와 같은 일을 하는 프로그램을 만들 수 있을 테지만 이렇게 짧고 쉽게 할 수는 없을 것이고 더구나 C 번역기를 바꿔서(!) 이 일을 할 수는 없을 것이다. 아마도 원래의 원시코드에 함수마다 코드를 더할 수밖에 없어서 원시 코드가 길다면 (수천 수만 행이라면) 아주 지루하고 시간이 많이 걸릴 것이다.

연산자 바꾸기

우리는 모두 C가 아주 인기있는 범용 언어라는 것을 안다. 따라서 C에서 번역기를 바꿀 수 없다면 그것은 중요한 게 아닐 거라고 주장할 수도 있다. 그러나, 포스가 강조하는 것은 당신이 원하는 것은 무엇이든 할 수 있다는 것이다. 프로그래머는 포스의 어떤 성질이라도 아주 쉽게 바꿀 수 있다. 프로그래머는 포스를 찰흙처럼 주물러서 해답을 만든다. 프로그래머는 자료 구조나 함수(절차) 같이 언어가 규정하는 것들을 가지고 궁리하는 대신 문제를 묘사하는 낱말들을 포스에 더해서 문제를 해결한다.
지금까지 말한 포스의 장점은 C에서도 가능하다고 반박할지 모른다. (비록 더 길고 복잡하고 원시코드를 고쳐야 할지도 모르지만) 윤곽잡개 프로그램을 짤 수 있고 필요하다면 printf도 고쳐 쓸 수 있을 것이다. 이제 C로는 도저히 할 수 없는 일을 예로 들겠다.
printf가 마음에 들지 않는다면 앞의 예에서처럼 C 프로그래머는 이것을 고칠 수 있다. 그러나 + 연산자가 마음에 들지 않는다면 어떻게 하겠는가? 잠시 동안만 덧셈의 의미를 바꿀 필요가 있다면 어떻게 하겠는가? C 번역기의 아주 깊숙한 부분에 이것이 있고 번역기의 원시코드를 가지고 있지 않기 때문에 C 프로그래머는 아무 것도 할 수 없다. 핵커가 되어 C 번역기를 역어셈블하고 실행코드를 바꿔서 C 번역기를 고칠 수 있을지 모르지만 이것은 정말로 극단적인 경우로 C 번역기 회사는 이것을 좋아하지 않을 것이고 문제가 생겼을 때 도와주지도 않을 것이다. 어떤 번역기 회사는 자기들의 권리을 침해했다고 주장할지도 모른다. 따라서 이것은 문제를 해결하는 옳은 방법일 수 없다. C 프로그래머는 어쩔 수 없이 그가 원하는 + 연산자를 제공하는 다른 C 번역기를 구할 수밖에 없다. (요즘은 C++를 비롯한 많은 언어에서 연사자 재정의를 지원하고 있다. 이 글이 쓰일 당시에는 연산자 재정의는 거의 혁명적이었을 것이다.)
포스에서는 산수 연산자를 포함한 모든 것이 낱말이다. 앞에서 .을 고쳤듯이 포스 프로그래머는 단지 +를 원하는 대로 다시 정의하면 된다. 전혀 어렵지 않다. 당신은 이렇게 말할 것이다. "제발 그만해 두라. 도대체 덧셈이 하는 일을 바꾸는게 당신한테 몇 번이나 필요했었는가?" 덧셈이 하는 일을 바꾸는 경우는 거의 없으리라는 것을 (그 드문 경우 중의 하나를 곧 예로 보이겠다) 나도 인정한다. 어쨌든 바꾸는 것은 가능하고 이 때문에 포스는 더 말랑말랑하고 더 강력한 언어이다. +를 고른 것은 단지 예를 보이기 위한 것이고 포스 프로그래머는 필요에 따라 어떤 낱말도 바꾸거나 없앨 수 있다. 포스는 벽 없는 방이라고 말하는 사람도 있다. 포스 프로그래머가 어떤 일을 하는데 어려움을 겪는다면 그 자신의 문제이지 절대로 언어 때문이 아니다. 포스의 어떤 면도 마음대로 바꿀 수 있기 때문에 프로그래머가 문제를 해결하지 못하는 것은 전적으로 프로그래머가 무능력하기 때문이지 프로그래머한테 필요한 어떤 기능이 포스에 없기 때문이 아니다. 비록 쉽지는 않지만 포스 프로그램의 문법을 C나 파스칼 또는 다른 언어와 비슷하게 고치는 것도 가능하다. (완전 어그로 끄는 말이다. 근데 곱씹어보면 전혀 틀린 말이 아니라는 게 함정.)
그런데 당신이 뒤붙임 표기법을 좋아하지 않는다면 어떻게 하겠는가? 다른 언어라면 당신은 아무 것도 할 수 없다. 포스에서 모든 것을 바꿀 수 있다면 이것도 고칠 수 있어야 할 것 아닌가? 맞다! 다른 언어처럼 "3 + 4"를 받아서 7을 출력하도록 포스를 고칠 수 있다. 지금 당신은 +의 행동이 바뀌기를 바라는 것이다. 새 + 연산자가 더미의 맨 위 값을 꺼내서 그것을 연산자의 뒤에 오는 수와 더할 것을 당신이 바라는 것이다. 보라, 조금 전까지만 해도 +를 바꿀 필요가 전혀 없을 것 같았지만 벌써 바꾸려 하고 있지 않은가? 가운데붙임 (infix) 표기법을 쓰도록 + 연산자를 바꾸는 게 얼마나 힘드냐고? 식은 죽 먹기다.
: + 32 WORD NUMBER + ;
: + 32 낱말 값 + ;
 
쉽지 않은가? 이제 "3 + 4"를 입력하면 7이 더미의 맨 위에 남는다. 다른 연산자도 이렇게 쉽게 바꿀 수 있다. 앞붙임 (prefix) 표기법을 쓰고 싶다면 그렇게 바꾸면 된다.

정의어 : 수퍼 배열 만들기

포스의 확장성에 대해 몇 가지 더 짚고 넘어 가기로 하자. 알다시피 C는 0으로 끝난 (null-terminated) 문자열을 쓰고 파스칼은 센 (counted) 문자열을 쓴다. 어느쪽이 나은 지에 대해 격렬한 논쟁이 있었고 양쪽 다 일리가 있다. 어쨌든 C에서 센 문자열을 쓰는 것은 어렵다. 파스칼에서 0으로 끝난 문자열을 쓰는 것도 어렵기는 마찬가지다. 포스에서는 보통 센 문자열을 쓰지만 약간의 코드를 더해서 둘 다 쓰게 할 수 있다. 포스에서는 다른 낱말을 정의하는 낱말을 정의할 수 있다. 이것은 다른 언어에 없는 포스의 또 다른 장점이다. 이 '정의어(defining word)'를 설명하려면 한참이 걸릴 것이므로 이 글의 끝에 소개한 책을 찾아 이에 관한 부분을 모두 읽기 바란다. 정의어를 만드는 능력은 참으로 대단하고 다른 언어에는 이와 비슷한 기능도 없다. 아래에 짧은 예를 보인다.
다른 낱말을 정의하는 낱말을 정의할 수 있다고 했다. 배열를 정의하고 싶다고 하자. 이 배열은 좀 특별해서 지적한 원소의 값뿐 아니라 그 원소의 값이 꺼내진 회수와 메모리 주소도 돌려주어야 한다. 파스칼이나 C나 다른 언어라면 배열의 값을 꺼내기 전에 실행될 코드를 따로 아래처럼 작성해야 한다.
access_my_array(xyz, element_num, count, address); { 파스칼 }
이것은 파스칼에서 배열의 값을 꺼내는 보통 방법인
my_array[j] { 1차원 배열의 경우 }
보다 훨씬 불편하다.
💡
Swift 등의 언어에서는 [] 의 행동을 지정할 수 있다. 분명히 현대 언어는 점점 말랑말랑해지고 있다.
포스에서는 배열을 아래처럼 정의하고
100 ARRAY Normal_Array
100 배열 보통배열
아래의 보통 문법으로 이 배열의 값을 꺼낸다.
45 Normal_Array @
45 보통배열 @
이것은 "보통배열"의 45번째 값을 더미의 맨 위에 올린다. 포스에서는 새 자료형을 만드는데 정의어를 쓸 수 있고 이 자료형이 실행될 때의 행동을 마음대로 바꿀 수 있다.
: super_array CREATE .... DOES> .... ;
: 수퍼배열 짓기 .... 하기> .... ;
super_array my_array ( super_array인 my_array를 만든다 )
수퍼배열 내배열
이 배열의 값을 꺼내는 방법은 보통 배열의 경우와 같다.
45 my_array @
45 내배열 @
 
하지만 이 경우에 "내배열"이 "수퍼배열"이기 때문에 한 값 대신 세 값이 (45번째 값, 지금까지 읽힌 회수, 45번째 값의 주소가) 더미에 얹힌다. 이렇게 (포스 프로그래머에게) 익숙한 문법이 한결같이 쓰이고 읽힌 회수를 증가시키거나 45번째 값의 주소가 얼마인지 당신이 신경쓸 필요가 없다. 이것들은 내배열이 수퍼배열이기 때문에 자동적으로 해결된다. (옮긴이: 어디서 들어본 말 같지 않은가? 1970년 근처에 만들어진 포스에 이미 객체 지향적인 개념이 들어있다.) 이 일을 하는 코드는 수퍼배열의 정의 안에 들어 있어야 한다. 어쨌든 수퍼배열과 보통배열을 사용하는 방법은 (프로그래머가 다르게 하고 싶지 않다면) 같다.

포스의 깊은 곳

포스 통역기의 진실

포스의 사전은 프로그래머가 요구하는 대로 늘었다 줄었다 할 수 있다. 프로그램 1, 5, 10, 100 개를 사전에 올리고 원하는 순서대로 실행시킨 다음 잊어버리고 (잊기[FORGET]는 이때 사용하는 포스 낱말이다) 다른 프로그램을 올릴 수 있다.
포스에는 오직 하나의 사전이 있지만 프로그래머가 원하면 낱말들을 용어별로 나누어 놓을 수 있다. 이렇게 나눠놓으면 프로그래머가 정해 놓은 순서에 따라 어떤 용어를 먼저 찾고 다른 용어를 차례로 나중에 찾는다. 이렇기 때문에 같은 이름을 가진 낱말이 둘, 셋 이상 있어도 헷갈리지 않고 쓸 수 있다.
낱말의 이름과 그 낱말의 정의를 가리키는 포인터가 포스 사전에 들어 있다. 한 정의를 택해 하나하나 따라가며 어떻게 번역이 되는지를 보자.
: SQUARE ( n -- n*n ) ( 이 낱말은 n의 제곱값을 계산한다 ) DUP * ;
: 제곱 ( n -- n*n ) 베껴 * ;
 
: 번역기가 SQUARE를 보면 이것이 새로 만들어질 낱말의 이름인 줄 안다. 번역기는 SQUARE라는 이름의 낱말이 들어갈 자리를 사전에 준비한다. 다음으로 번역기가 보는 낱말은 DUP이다. 번역기는 SQUARE의 내용이 될 낱말들의 주소를 두는 곳에 DUP의 주소를 찾아 써 넣는다. 지금까지 한 일을 아래에 보인다.
사전의 자리 포인터 메모리의 다른 곳 ---------------- -------- ----------------------- 메모리 주소 1005에 SQUARE 1005 197 ( DUP의 주소 ) 9872 ( *의 주소 ) 598 ( ;의 주소 )
이때 197, 9872, 598은 각각 낱말 DUP, *, ;의 코드가 있는 주소이다.
여기서 보면 SQUARE는 결과가 크든 작든 상관하지 않는다. 이 정의에서는 결과가 너무 커서 정수의 범위를 벗어나도 (넘쳐도) 알 수가 없다. 너무 커서 넘치지 않는지를 프로그래머가 알고 싶다면 약간의 코드를 더해 이를 확인할 수 있다. 그러나 프로그래머가 어떤 이유로 넘치는 경우가 생길 수 없다는 것을 미리 알고 있다면 굳이 두 번 확인해서 SQUARE를 느리게 만들 이유가 있겠는가? 이것은 포스가 빛나는 또 다른 경우이다. 실행 프로그램은 프로그래머가 원하지 않는 코드를 포함하지 않는다. 에러를 확인해야 한다면 필요한 자리에 써 넣으면 된다. 써 넣는 것은 간단하지만 실행시키는데는 (루프 안이라면 더욱) 시간이 걸리기 때문에 포스에서는 프로그래머가 이것을 결정한다. 포스를 쓰면 컴퓨터의 모든 자원을 사용하는 데 최대의 효과를 얻을 수 있다. 이 예에서는 프로세서가 불필요한 에러 확인을 하는데 시간을 낭비하지 않는다.
위에서 보았듯이 번역된 SQUARE의 코드는 실행시킬 수 있는 코드가 아니라 주소들의 리스트이다. 따라서 포스 프로그램을 실행시키려면 번역기가 필요하다. "이제 그만하자. 결국 이것은 베이식처럼 통역(interpreting)된단 말 아닌가?"라고 당신이 말하려 할 지 모르지만 아니다. 이것은 베이식과는 아주 다르다. 포스는 통역 베이식보다는 훨씬 빠르기 때문에 통역기가 있다는 것을 믿을 수 없을 것이다.
포스 프로그램이 실행되는 동안 실행되는 통역기는 엄청나게 빠르다. 이 통역기는 두가지 일만을 한다.
  1. 프로그램 카운터를 증가시킨다.
  1. 주소 리스트의 다음 주소로 뛰어간다.
이래서 이 통역기를 주소 통역기 (address interpreter) 또는 내부 통역기(inner interpreter)라고 부른다. 68000을 프로세서로 쓰는 기계의 경우 22 기계 주기(machine cycle)를 사용하는 기계 명령어 2 개로 이 일을 할 수 있다. 이것은 매우 매우 빠른 것이다. 실행시키는 데 다른 일을 해야 하는 것은 사실이다. 그래서 포스 프로그램은 다른 언어로 짠 프로그램보다 느리다, 맞지?
평균적으로는 그렇다. 그러나 앞에서 말한 윤곽잡개 프로그램이 있다. 이것은 이런 때 아주 유용하다. 윤곽잡개로 어떤 낱말에서 가장 오랜 시간을 보내는지 알 수 있고 이 낱말을 어셈블리어로 다시 쓸 수 있기 때문에 (포스 환경에서 바로 어셈블리어를 쓸 수 있다) 결국 얻어진 포스 프로그램은 완전히 어셈블리어로 짠 프로그램 말고는 가장 빠르다.
뭐라고? 당신이 어셈블리어를 싫어한다고? 상관없다. 상품으로 나와 있는 포스 시스템 중에 최적화 기능이 든 것이 많이 있다. 이것을 쓰면 당신이 쓰는 기계의 기계 명령어를 모르더라도 프로그램의 일부를 (전부를 선택했다면 전부를) 기계어로 바꾸어 줄 것이다.

가장 효율적인 언어, 포스

내가 앞에서 말했듯이 포스가 C만큼 널리 쓰이는 언어는 아니다. 그러나 기계의 능력을 최대로 발휘할 것을 요구하는 실시간 (real time) 프로세스 제어 만큼은 포스가 자주 쓰이는 분야이다. 포스가 느리다면 이 곳에 쓰일 리가 있겠는가? 미항공우주국(NASA)에서 스페이스 셔틀 연구를 하는데 포스를 쓰고 캘리포니아 파사데나에 있는 제트 추진 연구소(Jet Propulsion Laboratories)도 우주와 관련된 연구에 포스를 쓴다고 들었다. 포스는 엄청나게 효율이 좋고 기계에서 가장 많은 것을 얻어내는 데는 (물론 어셈블리어를 빼고) 이보다 좋은 언어가 없다.
찰스 무어와 동료들은 기계어가 포스인 마이크로프로세서를 만들었다. NOVIX 4000이라고 불리는 이 칩은 눈부시게 빠르다. 이것은 포스 프로그램을 실행시키는데 통역기가 필요 없다. 포스가 이것의 기계어이다. 이것은 보통 10MHz에서 4 MIPS의 속도를 낸다. IBM PC 호환 컴퓨터의 슬롯에 바로 끼울 수 있는 이 칩을 사용한 보드를 구할 수 있다. (옮긴이: 16비트 포스 프로세서는 NOVIX를 개량한 RTX2000으로 이어지고 32비트 포스 프로세서로는 SC32가 있다. 이들을 이용한 삽입 보드 [plug-in board] 컴퓨터와 한 보드 [single board] 컴퓨터를 Silicon Composers Inc.에서 구할 수 있다. 다른 프로세서를 이용한 한 보드 컴퓨터 중에도 포스를 쓸 수 있는 것이 많다.)

포스의 장점과 단점

포스의 장점

포스 코드는 매우 짧다. 앞에서 보았듯이 포스 번역기가 코드를 번역하는 방법 때문에 이렇게 된다. 포스 프로그램은 두 바이트 주소의 리스트로 번역이 되지만 어셈블리어는 둘, 넷, 여섯 바이트의 기계 명령으로 구성되기 때문에 번역된 포스 코드는 같은 일을 하는 어셈블리어 코드보다도 짧다. 두 바이트 주소를 쓴다면 어떻게 64 K바이트 이상의 메모리에 대해 주소를 지정하느냐고? 오래된 포스 시스템은 할 수가 없어서 64 K바이트밖에 쓸 수 없었다. 요즘의 포스 시스템들은 기계가 가진 모든 메모리를 사용할 수 있다. 사전의 구조와 관련된 이 기법은 상당히 고급이어서 여기서 다룰 수 없다. 이것이 가능하고 아주 효율적이라고만 말하고 지나 가기로 한다.
포스가 어셈블리어를 제외한 다른 어떤 언어보다도 빠르다는 것은 IBM PC 호환기종에 쓰는 HS/FORTH로 확인할 수 있다. 같이 따라오는 최적화 기능을 쓰면, 최적화 기능을 사용한 모든 회사의 C, 파스칼, 포트란을 비롯한 다른 어떤 언어보다도 빠르다. "에라스토테네스의 체"나 다른 벤치마크 시험으로 확인할 수 있다. 벤치마크 시험의 결과를 너무 믿어서는 안된다는 것은 나도 알지만 어쨌든 나를 믿어라. 최적화 기능을 사용한 포스는 (어셈블리어를 제외한) 다른 어떤 언어보다도 빠르다. 최적화 기능을 사용하지 않고도 포스는 최적화시킨 C 코드의 1/2 정도의 속도를 낸다. RF 안테나에 관한 무엇을 계산하고 그림을 그리는 (내가 이해하지 못 했기 때문에 더 자세히 설명할 수 없다) 프로그램을 짠 사람과 이야기해 본 적이 있다. 그는 (최적화하지 않은) 포스와 터보 파스칼과 Lattice C로 프로그램을 각각 짜 보았는데 포스가 가장 빨랐다고 말했다. 파스칼이 가장 느렸고 포스는 C보다 10 % 정도 빨랐다고 한다. 어떤 것들은 C나 파스칼로 짜는 것이 분명히 더 빠를 것이다. 당신이 하고 있는 일이 어떤 경우일지는 미리 알 수 없다. (옮긴이: 조금 뒤에 글쓴이가 말하겠지만 이 글은 1987년에 쓰인 것이다.)
포스의 다른 장점은 포스 시스템에는 보통 편집기, 번역기, 어셈블러, 통역기(물론 주소 통역기와 명령 통역기 둘 다), 표준 사전이 모두 따라오고 이것들이 모두 합쳐서 50 K바이트 밖에 차지하지 않는다는 것이다. 포스는 지금까지 만들어진 거의 모든 개인용 컴퓨터에서 실행시킬 수 있다. 메모리가 24K바이트밖에 되지 않는 래디오색(Radio Shack) TRS-80 컴퓨터에서도 포스는 사용자가 쓸 메모리를 충분히 남겨 놓는다.
포스의 또 다른 좋은 점은 번역 속도가 빠르다는 것이다. 번역기가 해야 할 일이 간단하기 때문에 번역 속도가 상당히 빠르다. (볼란드사의 터보 번역기들을 제외하면) 대부분의 C나 파스칼 번역기는 원시코드 4000 행을 번역하는 데 3 내지 5 분이 걸린다. 포스는 1 분 정도밖에 걸리지 않는다. 볼란드사의 제품들은 포스보다 번역 속도가 더 빠를지도 모르지만 내가 써보지 않았기 때문에 뭐라고 말할 수 없다. HS/FORTH를 비롯한 어떤 포스는 해싱(hashing) 기법을 써서 분당 10,000 행까지 번역 속도를 높였다. 포스는 확장할 수 있기 때문에 어떤 포스에도 같은 기법을 써서 번역 속도를 높일 수 있다. (포스만이 진정한 의미에서 확장할 수 있다. 다른 언어도 확장할 수 있다고 주장하지만 포스에서 말하는 의미로는 아니다.)
포스의 다른 좋은 점은 바깥 통역기 즉 명령 통역기를 써서 프로그램을 대화식으로 짤 수 있다는 것이다. 사용하기 전에 원시 코드의 조각들을 입력하고 실행시켜서 완벽한지를 시험할 수 있다. 번역기와 통역기가 모두 있는 환경에서 C로 일해 본 사람이라면 내가 말하는 것이 얼마나 좋은지 알 것이다.
🖋
내가 Swift와 Playground를 좋아하는 이유가 이것이다.
옛 포스 시스템은 자료의 블록을 편집하는 블록 편집기를 썼고 이것은 그 당시의 시스템과 잘 어울렸다. 지금은 컴퓨터가 운영 체제를 갖추고 있기 때문에 요즘의 포스는 원시코드를 운영 체제의 표준 텍스트 파일에 저장했다가 번역할 수 있다. 어느 쪽에도 각각의 장점이 있지만, 블록 편집기를 새로 배울 필요가 없고 손에 익은 편집기를 쓸 수 있기 때문에 나는 텍스트 파일을 즐겨 쓴다. 어는 쪽을 쓸 건 지는 쓰는 사람한테 달렸다. 가지고 있는 포스 시스템에 텍스트 파일을 다루는 기능이 없다면 당신이 더하면 된다.

포스의 단점

지금까지 나는 내가 포스에서 좋아하는 점들을 말했다. 세상의 모든 것들처럼 좋은 게 있으면 나쁜 것도 있기 마련이다. 지금부터는 내가 보는 포스의 단점을 말하겠다.
 
  1. 어떤 시스템에서는 원시 코드를 저장하는데 텍스트 파일을 쓸 수 없다. 물론 프로그래머가 더하면 되지만 하루만에 되는 일은 아니다. (아마도 일주일이 걸릴 것이다.)
  1. 어떤 포스 시스템은 기능이 고루 갖추어져 있고 어떤 것은 그렇지 못하다. 함수 라이브러리의 구멍을 메꾸는데 시간을 보내지 않으려면 C 번역기를 살 때 필요한 기능이 다 들어 있는지 미리 확인하고 싶을 것이다. 포스에서도 마찬가지다. 포스에는 몇 가지 표준이 있고 포스 시스템은 보통 그 중의 하나를 따르지만 프로그램을 짜는데 필요한 모든 것이 표준에 정해져 있지는 않다. (옮긴이: 대표적인 포스의 표준은 79년에 정해진 "79-표준"과 83년에 정한 "83-표준"이다. 새로 정하는 "미표준국 표준 포스[ANSI Standard Forth]"가 거의 완성 단계에 있어 94년 중에 발표될 것이다.) 프로그래머는 포스 시스템의 구멍들을 자기가 짠 코드로 메워야 한다. 이점은 바로 앞에서 말했듯이 C도 마찬가지다.
  1. 포스는 아주 많은 자유를 준다. 어떤 사람은 밧줄이라고 말한다. 올가미를 만들어 거기에 당신 목을 맬 수도 있다. 포스가 당신한테 그 밧줄을 줄 때는 당신이 그걸로 다리 사이를 잇고 프로그램 세계의 가장 높은 봉우리에 올라 가라는 뜻이었다. 그러나 고의든 실수든 당신이 그것을 다른 목적에 쓰려고 한다면 포스는 당신의 목을 달아 맬 것이다. 포스를 쓸 때 개인용 컴퓨터를 먹통으로 만드는 것은 아주 쉽다. 포스를 써서 기계의 모든 부분을 마음 대로 조작할 수 있다. '당신 것이 아닌' 메모리에 어떤 값을 써 넣으려 한다면 포스는 상관하지 않는다. 그러나 운영체제는 그렇게 무심하지 않아서 결과는 에러 메시지이거나 그보다는 보통 기계가 먹통이 되는 것이다. 이것을 막기 위해 포스 프로그래머가 프로그램을 맨처음 실행시키기 전에 코드에 마련할 수 있는 방지장치가 있다. 실행시키고 나서 벌레가 없다고 생각될 때 (당신이 똑똑해서 프로그램을 잘 꾸몄다면) 스위치를 하나 바꾸어 이 방지장치를 끌 수 있다. 덜 똑똑한 프로그래머는 원시 코드에 코드를 덧붙였다가 나중에 없애야 할 것이다. 정의어와 "가리킴 실행(vectored execution)"이라 부르는 방법을 쓰면 원시 코드에 손을 댈 필요가 없다.
    1. 🖋
      그러니까 이 말은 포스는 안전하지 않다는 거다. 이점 때문에 현대의 프로그래머들에게 포스는 도전을 받고 있다. 친구에게 포스를 소개해줄 때 가장 먼저 들어야 했던 반박은, 소스 코드가 이상하다 등이 아니라 안전하지 않다는 거였다.
  1. 어떤 포스에서는 실수 (부동소수점) 연산을 할 수가 없다. 찰스 무어가 포스를 만들 때는 실수 연산을 일부러 뺐다. 그 당시 (1970년 근처)에는 컴퓨터에 빠른 실수 연산 보조프로세서가 없어서 실수를 쓰려면 속도가 많이 떨어지는 것을 감수해야 했다. 그래서 찰스 무어는 고정 소수점 연산법을 개발해서 그것을 대신 썼다. 지금(1987년)은 실수 연산 보조프로세서가 많이 나와 있고 포스는 이것을 아주 잘 쓸 수 있다. 어떤 시스템에는 들어 있지만 어떤 시스템에서는 당신이 필요한 코드를 짜야 한다. 미리 알아 보는 것이 좋다.
  1. 어떤 시스템에서 부동소수점 연산을 쓸 수 없는 것처럼 어떤 시스템에는 문자열을 다루는 낱말이 부족해서 프로그래머가 지어야 한다. 다시 말하지만 사기 전에 필요한 것이 다 들어 있는지 확인해야 한다. 내가 쓰는 문자열을 다루는 낱말들은 내가 만들었다. 어려운 일은 아니지만 이 일은 다른 사람에게 맡기고 바로 문제를 해결하는데 뛰어들고 싶은 사람도 있을 것이다.

포스의 또 다른 장점

  1. 포스에서는 2 에서 72 사이의 어떠한 수도 진법의 밑으로 쓸 수 있다. 원하는 밑 xx를 골라서 "xx BASE !"라고 입력만 하면 된다. 그리고 나면 모든 연산이 그 수를 밑으로 해서 이루어진다. 얼른 생각하기에 밑으로 쓸모가 있는 것은 2 (이진법), 8 (팔진법), 10 (십진법, 디폴트), 16 (십육진법)이지만 다른 경우도 있을 수 있다. 60진법은 시간을 세는데 쓰인다.
  1. 포스를 써서 올라가고 싶은 만큼 높은 수준으로 올라갈 수 있다. 가장 낮은 수준의 일을 하는데도 포스는 똑같이 편리하다. 비트을 다루기도 쉽고 인터럽트 처리기를 짤 수도 있다. 포스를 써서 관계형 데이타 베이스 운영 시스템을 만들 수도 있다.
  1. 한 낱말 안에 포스와 어셈블리어를 섞어 쓸 수 있다. 이것은 굉장히 편리하다. 벌레 잡기가 쉽고 키보드 입력과 화면출력을 어셈블리어로 할 필요가 없다.
  1. 자료로 얻은 것을 마치 코드인 것처럼 실행시킬 수 있는 LISP의 능력을 흉내낼 수 있다. 실행할 때 사용자로부터 문자열을 입력받는 프로그램을 짰다고 하자. 포스는 이 문자열을 포스 코드로 간주하고 번역, 실행시킨 다음 당신이 짠 프로그램으로 돌아올 수 있다. 프로그래머의 개입 없이 이 과정이 저절로 일어나게 할 수 있고 이것은 식은죽먹기처럼 쉽다. 당신의 응용에 필요하다면 이렇게 자료와 코드 사이의 구분을 없앨 수 있다.
    1. 🖋
      역시 정보 보안 측면에서 굉장한 취약점이다. buffer overflow를 그냥 할 수도 있다는 말이니까.
  1. 겨우 1,000 바이트 정도의 원시 코드를 더해서 객체 지향적 프로그램법을 쓸 수 있다. 나는 내 포스 시스템을 이렇게 확장했고 이것은 프롤로그(Prolog) 만큼 강력하다.
  1. 포스를 쓰면 프로그래머의 능률은 엄청나게 높아진다. 다른 프로그램 환경이 이렇게 주장하는 것을 들은 적이 있겠지만 포스는 언어이면서 동시에 프로그램 환경이다. 포스가 개발된 방법 때문에 아주 쉽게 코드를 쏟아 낼 수 있다.
    1. 먼저 해결하려는 문제를 자세히 연구한다. 문제의 핵심을 파악한 후에 맨 나중에 얻을 답에서 쓸 적당한 낱말들을 고안한다. 이렇게 해서 '주제어'들을 찾으면 바닥에서부터 필요한 낱말을 차례로 지어서 문제를 해결하는 뼈대가 될 이 주제어들을 만든다. 한 번에 한 낱말씩 코드를 짜고 벌레를 잡는다. 아래 수준에서 필요한 낱말이 모두 만들어지면 한 단계 위에서 필요한 낱말을 만들어 나간다. 마침내 (당신이 생각하는 것보다 훨씬 빨리) 처음에 생각한 낱말들에 이르게 될 것이다. 이것은 꼭대기에서 내려오는 (top-down) 프로그램법과 바닥에서 올라가는 (bottom-up) 프로그램법을 결합한 것이다. 이 방법을 쓰면 놀랄만큼 일이 잘 된다. 만들어진 프로그램은 잘 나뉘어 (modular) 있기 때문에 유지 보수 하기도 쉽다.
      포스에서 낱말은 아주 짧고 이해하기 쉽게 만드는 것이 좋다. 다른 언어에서는 함수를 부르고 값들을 확인하고 넘기는데 많은 일을 해야 하기 때문에 코드가 한 줄이나 두 줄인 함수를 쓰는 것이 비경제적이다. 그러나 포스에서는 이 댓가가 비싸지 않다. 포스는 이미 더미를 사용하고 있고 (프로그래머가 필요하다고 판단한 경우가 아니면) 값들을 확인하지 않는다. 결과적으로 포스 프로그래머는 다른 언어에 비해 아주 짧은 낱말을 많이 지을 수 있고 좋은 프로그래머라면 많이 지어야 한다. 이렇게 해서 다른 언어를 써서는 얻을 수 없는 잘 정리된 코드를 얻을 수 있다.
  1. 포스에서 "가리킴 실행(vectored execution)"이라 불리는 방법을 쓸 수 있다. 이것은 한 포스 낱말의 포인터를 바꿔서 다른 코드를 가리키게 하는 것이다. 그 결과 어떤 낱말도 이 낱말의 포인터가 가리키는 것에 따라 다른 행동을 보일 수 있다. 지금은 이 낱말이 한 값을 출력하지만 조금 뒤에는 세 값을 출력하고 또 조금 지나면 한 값만을 출력한다! 이것은 아주 아주 편리하다. (비록 덜 강력하기는 하지만) C에서도 함수를 가리키는 포인터 배열을 써서 비슷한 일을 할 수 있다.
  1. 포스 사전에 수록된 낱말들은 모두 한 층에 있기 때문에 어떤 낱말도 부를 수 있다. 그러나 포스 프로그래머가 원하면 '머리없는' 낱말을 만들 수 있다. (다시 말하지만 모든 것이 가능하다.) 포스 시스템을 파는 회사는 낱말의 머리를 자르지 않는다. 그런데 왜 머리를 자르냐고? 머리를 자르면 그 낱말을 사전에서 찾을 수 없게 되는 대신 몇 바이트를 절약할 수 있다. 어떤 프로그래머들은 잘못쓰면 아주 위험한 결과를 일으킬 수 있는 낱말의 머리를 잘라 실행시킬 수 없게 하지만 많은 사람들은 사용자에게 위험을 알리기만 하고 내버려 둔다.
  1. 포스는 자료형을 까다롭게 따지지 않는다. 포스한테는 모든 것이 수이다. 모든 자유가 그렇듯이 이것은 축복이며 동시에 저주이다. 사용자에게 자신의 이름을 입력하라고 해 놓고 글자들을 더해서 이름의 제곱근을 구할 수도 있다. 나에게는 쓸모가 없지만 이것이 필요한 사람이 있을 지도 모른다. 포스는 상관하지 않는다. 결국은 컴퓨터한테는 모든 게 수아니던가? 이름의 제곱근은 해싱 함수로 쓸 수 있을지도 모른다. 한 편으로 자료형을 까다롭게 따지고 싶다면 쉽게 그렇게 할 수 있다. 당신에게 달려 있다.
  1. 포스를 써서 프로그래머는 원할 때 기계의 모든 면을 제어할 수 있다. 이점은 어셈블리어보다 낫다. 어셈블리어에서는 당신이 언제나 모든 면을 제어해야만 한다. 프로그램에서 기계의 모든 면을 제어하는 것이 필요한 경우는 몇몇 순간일 것이다. 포스를 쓰면 필요할 때만 기계를 완전히 제어하고 그렇지 않은 경우에는 아래 수준에서 해야 할 상세한 일을 잊어 버릴 수 있다.
  1. 포스에서도 C에서처럼 필요한 입력의 갯수가 고정되어 있지 않은 낱말을 만들 수 있다. C 프로그래머에게 물으면 이것이 얼마나 편리한지 이야기해 줄 것이다. 또 다시 말하지만 포스에서는 모든 것이 가능하고 조금의 노력으로 거의 모든 경우에 원하는 것을 실현할 수 있다! 이 경우 포스 낱말은 더미에서 입력을 받고 더미로 출력을 하기 때문에 몇 개를 입력으로 받고 몇 개를 출력으로 얹을지는 전적으로 당신에게 달려 있다. 입력의 갯수를 고정할 지 가변으로 할 지 결정하는 것은 당신이다.
  1. 어떤 포스 낱말도 실행할 때 실행되게 하거나 아니면 번역할 때 실행되게 할 수 있다. 이것은 믿어지지 않을 정도로 편리하다. 이것을 써서 다른 언어에서 들어본 적이 없을 정도로 시스템을 입맛에 맞게 고칠 수 있다.
    1. 🖋
      컴파일 시간에 실행되는 매크로나 컴파일 시간 상수 등을 떠올리면 이해하기 쉬울까? 포스는 소스 코드를 번역할 때와 실행할 때 서로 다르게 행동하도록 낱말을 만들 수 있다. 처음에 이 개념을 이해하느라 굉장히 애를 먹었다.

끝판왕: 벽 없는 방

도전장

모든 것을 바꿀 수 있는 언어가 믿어지지 않을 정도로 편리하다는 것을 보이는 마지막 예로 다음을 보자. 나는 이 문제를 여러 전문적인 프로그래머에게 보였었다.

다음 일을 하는 함수를 작성하라. (여기서 함수는 절차, 낱말, 코드 한 조각을 말한다.)
  • 정수값을 갖는 한 변수가 있다고 하자. (이것을 TRACE라고 부르자.)
  • 프로그램을 번역할 때 TRACE의 값이 0이면 정상적으로 번역한다.
  • 그러나 TRACE의 값이 1이면 번역할 때 다음 일을 하는 코드가 실행 프로그램에 덧붙는다.
    • 프로그램의 각 딸림 함수(sub-fucntion)를 부를 때마다 프로그램의 흐름을 따라가는데 필요한 정보를 표시한다.
      • 이 정보는 선택한 변수의 값, 어떤 메모리 주소의 내용 등을 포함한다.
    • (프로그램이 실행되는 동안) 따라가기 정보를 보이고 나서 그 딸림 함수의 정상적인 코드를 실행한다.
  • 이 벌레잡기/따라가기 함수의 코드는 프로그래머가 작성한 것이어야 한다. 프로그래머는 이 함수를 마음대로 바꿀 수 있고 필요하다면 매 분마다 그렇게 할 수 있어야 한다.
  • 이때 딸림함수의 '정상 코드'를 바꿀 필요가 없어야 한다.
    • 큰 응용 프로그램의 모든 딸림함수에 원시 코드를 덧붙이는 것은 너무 번거로우므로 이를 요구해서는 안된다. (이 따라가기 정보는 나중에 필요하게 된 것이지 프로그램을 짜기 시작할 때는 결정되어 있던 것은 아니다. 길게 보면 이것이 따라가기 프로그램을 사용하는 정상적인 상황이다.)
    • 따라서 C 프로그래머는 전처리기나 IFDEF와 같은 조건부 번역문을 사용할 수 없다. 왜냐하면 이것을 쓰려면 필요한 모든 printf문을 원시 코드에 더해야 하기 때문이다. 각 딸림함수의 원시 코드는 '정상적인' (즉, 따라가기 프로그램을 쓰기 전) 모양 그대로 남아 있어야 한다.
  • 이것을 해결했다면, 당신 프로그램의 원시 코드가 번역되는 동안 TRACE 변수의 값을 바꿀 수 있어서 선택된 딸림함수만이 따라가기 정보를 표시하도록 따라가기 함수를 고쳐라.
 
이것이 내가 여러 전문적인 프로그래머에게 보였던 문제이다. (그들이 다뤄본 프로그램 언어는 아주 많다.) 그들 모두가 똑같은 대답을 했다.
"당신은 벌레잡개(debugger) 프로그램을 써야 한다. 벌레잡개 프로그램으로 당신은 원하는 것을 모두 할 수 있다."
내 대답은 "아니오"였다. 벌래잡개는 언어의 일부가 아니다. 그것은 어떤 번역기회사가 그 언어에 더한 것이다. 그리고 만약 벌레잡개 프로그램에 내가 원하는 기능이 없다면 나는 그 벌레잡개를 고칠 수 없다. 몇 사람은 번역기의 원시 코드를 구한다면 쉽게 (길게 잡아 수백 행 정도의 코드로) 그 일을 할 수 있을 거라고 말했다. 그것도 역시 해답일 수 없다. 번역기의 원시 코드는 엄청나게 비싸거나 (번역기 가격의 열 배 이상) 많은 회사들은 아무리 비싼 값에도 팔지 않는다.
따라서 사람들은 불가능한 일이라고 말했다. 글쎄... 당신은 이미 대답을 안다. 포스에 아주 쉽고 우아한 해결책이 있다.
 
다음은 이 문제를 해결하는 한 방법을 포스로 짠 것이다.
 
VARIABLE TRACE ( TRACE가 1이면 번역기가 지금 정의하는 낱말에 당신이 ) ( 더하고 싶은 코드를 넣는다. ) : Line.of.Dashes ." --------------------------------------" CR ; ( Line.of.Dashes는 따라가기 정보를 예쁘게 보이기 위한 것이다. ) : My.Trace.Code Line.of.Dashes ." Begin Trace Data." CR ( 여기에 알고 싶은 값들을 보이는 코드를 넣는다. ) ( 아래 코드는 예로 보인 것이다. ) ." Current Stack Contents:" CR .S CR ." End Trace Data." CR Line.of.Dashes ; : : [compile] : TRACE @ IF COMPILE My.Trace.Code THEN ; IMMEDIATE
 
(옮긴이: 피그미 포스에서는 : 대신 ;My.Trace.Code를 포함시키는 것이 편하다.)
COMPILER : ; TRACE @ IF COMPILE My.Trace.Code THEN POP DROP \ ; ; FORTH )
 
무른값 따라가기 : 긴.줄 ." --------------------------------------" 다음줄 ; : 따라가며.보여 긴.줄 ." 따라가며 보이기 시작." 다음줄 ." 지금의 더미 내용:" 다음줄 .더미 다음줄 ." 따라가며 보이기 끝" 다음줄 긴.줄 ; 번역기용어 : ; 따라가기 @ 면 번역 따라가며.보여 라 되돌이> 버려 \ ; ; 포스용어
 
이게 전부다. 이제 끝났다.
 
이게 어떻게 답이냐고? 번역기를 바꾸어서 번역기가 "따라가기"라고 불리는 변수의 값을 보게 한 것이다. 그 값이 0이 아니면 지금 번역되고 있는 루틴의(즉, 프로그램의 각 함수[낱말]의) 정의에 "따라가며.보여"를 부르는 코드를 더한다. 이렇게 해결하는데 원시 코드는 하나도 바뀌지 않았다. 당신은 단지 "따라가기"의 값을 1로 바꾸고 (1 따라가기 ! 를 입력해서 바꿀 수 있다) 따라 갈 프로그램을 번역한 후 실행하면 된다.
 
포스에서 이 해답은 아주 짧고 아주 우아하다. 다른 언어로 해결할 수 있다 하더라도 이렇게 간결하지는 않을 것이다. 내 생각에 LISP에는 답이 있고 어쩌면 SMALLTALK-80에도 답이 있을지 모른다. (후자는 자신이 없다. 원시어[primitive]는 바꿀 수는 없는데 번역기가 어쩌면 원시어일지도 모른다.)
그러나 포스보다 인기있는 유력한 언어 중에는 답이 없는 게 확실하다. 포스로 당신은 무엇이든 할 수 있다. 포스 세계에는 벽이 없다--당신이 가지고 온 것을 빼고는.
위의 답이 너무 길다고 생각되면 알짜만을 남겨서 다음처럼 줄일 수 있다.
 
VARIABLE TRACE : My.Trace.Code ." Current Stack Contents:" .S ; : : [compile] : TRACE @ IF COMPILE My.Trace.Code THEN ; IMMEDIATE
(옮긴이: 피그미 포스에서는 다음처럼 한다.
COMPILER : ; TRACE @ IF COMPILE My.Trace.Code THEN POP DROP \ ; ; FORTH
)
무른값 따라가기 : 따라가며.보여 ." 지금의 더미 내용:" .더미 ; 번역기용어 : ; 따라가기 @ 면 번역 따라가며.보여 라 되돌이> 버려 \ ; ; 포스용어
 
물론 당신이 프로그램을 따라가는데 필요하다고 생각하는 어떤 일도 "따라가며.보여"에 더 할 수 있다. 함수 (더 정확하게는 낱말) 정의 바깥에 넣은 말은 번역할 때 실행되므로 번역하는 동안 "따라가기"의 값을 바꿀 수 있다.
1 TRACE ! ( 번역하는 동안 TRACE를 1로 설정한다.) : Show.Me ........ ; ( 실행될 때 따라가기 정보를 표시한다.) 0 TRACE ! ( 번역하는 동안 TRACE를 0으로 설정한다.) : No.Show.Me ..... ; ( 실행할 때 따라가기 정보를 표시하지 않는다.)
 
(옮긴이: 위의 따라가기 프로그램을 아래처럼 우리말에 더 가깝게 고칠 수 있 다.
무른값 따라가기 : 따라가기? 따라가기 @ ; : 켜 1 맞바꿔 ! ; : 꺼 0 맞바꿔 ! ; : 따라가며.보여 ." 지금의 더미 내용:" .더미 ; 번역기용어 : ; 따라가기? 면 번역 따라가며.보여 라 되돌이> 버려 \ ; ; 포스용어
 
;를 다시 정의하고 아래처럼 보이며.버려안.보이며.버려를 정의하고 실행시켜보라.
따라가기 켜 : 보이며.버려 버려 ; 따라가기 꺼 : 안.보이며.버려 버려 ; 11 22 33 44 55 66 보이기.버려 안.보이기.버려 보이기.버려 안.보이기.버려 보이기.버려 안.보이기.버려
)
포스를 벽 없는 방이라고 부르는 것은 옳다.
 

참고 문헌

내가 본 책 중에는 아래 책이 포스를 배우기에 가장 낫다.
FORTH: A Text and Reference by Mahlon G. Kelly and Nicholas Spies Published by Prentice-Hall in 1986
 
내 생각에는 위 책만 못하지만 아래 책도 포스를 배우는데 널리 쓰인다.
Starting FORTH by Leo Brodie Published by Prentice-Hall in 1981 같은 사람이 쓴 2판이 나와 있고 이것은 1판보다 나을 것이다.
 
위에 소개한 책과 당신의 컴퓨터에서 쓸 수 있는 공개 포스 시스템을 당신이 하나씩 구하기를 진심으로 바란다. 공개 포스 시스템은 부족한 구석이 있겠지만 정의어를 비롯하여 모든 것을 더하고 빼고 바꿀 수 있는 포스의 특색은 모두 가지고 있으므로 불가능해 보이는 것을 가능하게 하는 것을 포스의 능력은 충분히 맛볼 수 있을 것이다.
 
(옮긴이: 옮긴이는 위 책을 모두 가지고 있고 포항공대 도서관에 1987년에 출간된 Starting Forth 2판이 있다. 여기 자료실에서 IBM PC에서 쓸 수 있는 포스인 hForth 를 찾을 수 있다.)
 
이 긴 글을 읽어 주어 고맙다. 포스로 프로그램 짜는 것을 당신이 심각하게 고려하도록 이 글이 자극이 되었으면 좋겠다. 포스가 널리 쓰이면 우리 모두가 얻는 것이 정말로 많을 것이라고 생각하기 때문에 시간을 들여 이 글을 썼다.
포스는 벽없는 방이다. 속임수가 아니고 과장이 아니다. 정말로 포스는 가장 강력하고, 말랑말랑하고, 확장하기 쉽고, 일이 잘 되고, 만족스러운, 따라서 가장 바람직한 언어이고 환경이다. 그리고 지금까지 만들어진 거의 모든 컴퓨터에서 포스를 쓸 수 있다. 오늘은 찾아다녀야 포스가 쓰이는 것을 볼 수 있겠지만 조만간 가는 곳 어디서나 포스를 볼 수 있게 되기를 바란다.
 
Roy E. Brunjes (CompuServe ID# 71310,1427) 3/16/88

 
위 글은 Roy Brunjes가 쓴 "Why is FORTH so great?"라는 제목의 글을 번역한 것이다. 1988년에 쓰인 글이라 어떤 부분은 시대에 뒤떨어진 감이 있지만 포스의 특징을 비교적 잘 설명하고 있다. 많은 사람이 이 글을 읽고 포스에 관심을 가지게 되기를 바란다.
포스를 접해본 사람은 그렇게 많지 않을 것이다. 한국과학기술원의 변종홍 님에 의해 애플 II 컴퓨터에 이식된 우리말 포스 "늘품"에 관한 기사가 잡지에 몇 번 소개되었고 교학사에서 1988년에 "FORTH 입문"이라는 책이 출판된 것 외에는 포스가 우리나라에 소개된 것은 거의 없는 것 같다.
* 포스는 우리말에 가장 어울리는 컴퓨터 언어이다. 베이식 언어에서 지정어를 한글로 바꾼다고 프로그램이 바로 우리말이 되지는 않는다. 포스는 나중붙임 (postfix) 표기법을 쓰기 때문에 목적어가 동사 앞에 오는 우리말에 가깝다. 하지만 그보다 더 중요한 것은 포스를 쓰면 사용자가 문법을 만들 수 있다는 것이다. 프로그래머에게 모든 것이 허용되어 있기 때문에 우리말에 맞추어 포스를 고쳐 쓰면 된다.
옮긴이는 포스가 C나 C++의 자리를 차지하리라고는 믿지 않지만 우리말 컴퓨터 언어를 만드는데는 가장 좋은 출발점이라고 생각한다. 옮긴이의 바람은 어린이들이 우리의 생각틀로 프로그램 만드는 법을 배울 수 있게 되는 것이다. 관심있는 분들의 연락을 바란다.
 
1993.10.7 고원용 대전시 유성구 장동 100 한국화학연구소 무기소재부 340-343 wykoh (HITEL) wykoh (천리안) WYKOH@PADO.KRICT.RE.KR
 
 
  • 박진묵;변종홍, "늘품" 마이크로소프트웨어 1987년 9월 178-189.
  • 변종홍, "컴퓨터가 우리말을 알아듣는다" 과학동아 1988년 2월 157-161.
  • 변종홍, "우리말 무른연모도 만들자" 과학동아 1988년 3월 156-161.
  • 이태욱, "FORTH 입문" 교학사, 1988. -- 이 책만 가지고 포스를 배우기에는 내용이 충분치 못하지만 우리말로 출판된 유일한 책이다. 현재 시중에서 2쇄를 구할 수 있다.
IBM PC에서 쓸 수 있는 포스인 hForth 0.9.7을 자료실에서 찾을 수 있다.
 
 
피그미에서 다음을 입력하면 우리말로 쓴 예들을 실행시킬 수 있다. 한글 DOS를 사용하고 있지 않다면 피그미를 실행시키기 전에 "다른안시한글"과 같은 에뮬레이터를 먼저 올려야 한글을 쓸 수 있다.
 
: 0> 0 > ; ( 피그미에 들어 있지 않은 0>와 ) COMPILER : [COMPILE] \ \ ; FORTH ( [COMPILE]을 이렇게 정의한다. ) : 베껴 DUP ; : 버려 DROP ; : 맞바꿔 SWAP ; : 낱말 WORD ; : 값 NUMBER ; COMPILER : 면 \ IF ; FORTH COMPILER : 아니면 \ ELSE ; FORTH COMPILER : 라 \ THEN ; FORTH : 무른값 VARIABLE ; : 다음줄 CR ; : 번역기용어 COMPILER ; : 포스용어 FORTH ; : .더미 .S ; : 번역 COMPILE COMPILE ; : 되돌이> POP ;