AndreaSmalltalkLecture:SmalltalkLecture 03

From 흡혈양파의 인터넷工房
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
3. 객체로 채워진 세상

객체로 채워진 세상

앞의 마당에서 여러분은 Smalltalk의 객체에 대해서 아주 기본적인 것들을 공부했습니다. 우선 객체란 무엇인지, 그리고 이런 객체에게 어떻게 명령을 내리는지, 또한 객체가 우리의 지시를 실행하고 나서 만들어진 결과를 어떻게 처리하는지에 대해서도 알아보았습니다. 또한 Smalltalk에서 프로그래밍을 할 때 언제나 중요한 역할을 하고 있는 일터(workspace)와 객체 탐색기(object inspector)에 대해서도 알아보았습니다.


이를 바탕으로 이제 이번 마당에서 여러분은 보다 복잡하면서도 재미있고, 또한 프로그래밍에 유용한 여러 가지 객체들에 대해서 공부하게 될 것입니다. 앞의 마당에서 다루었던 객체들이 주로 숫자, 글줄, 글자 등과 같이 단순한 객체들인데 비해, 이번 마당부터는 알림판(transcript)이나 글줄 입력 상자처럼 눈에 보이고 실제적으로 유용한 기능을 수행하는 객체에 대해서 알아볼 것입니다. 또한 몇 개의 그림 객체에 대해서도 알아볼 것입니다.


이 마당을 공부하는 중에 여러분은 Smalltalk 에서 매우 중요한 문법 몇 가지를 공부하게 될 것입니다. 물론 이러한 문법 사항들이 C/C++ 처럼 복잡하고 방대한 것은 아닙니다. 그냥 보기로 제시되는 글토막들을 가늠하다보면 충분히 이해할 수 있을 것입니다. 어찌 보면 멀기도 먼 과정이겠지만, 한 걸음씩 진행하다보면 어느덧 셋째 마당의 끝에 다다랐을 것입니다. 힘을 냅시다.


그럼 먼저 Smalltalk 환경의 알림 창구라고 할 수 있는 "알림판" 에서부터 시작합시다.


알아보자, 알림판!

지금 Smalltalk를 실행하지 않은 사람들은 Dolphin Smalltalk를 실행시켜 봅시다. 그러면 몇 개의 창이 눈에 보일 것입니다. 지금까지 우리가 여러 가지 글토막을 가늠했던 일터(workspace)와 함께 "알림판"(transcript)라고 하는 창이 눈에 들어올 것입니다. 겉으로 봐서는 알림판은 일터와 크게 달라 보이지 않습니다.


그림 3-1::일터와 알림판 (Stp3-1.gif)


사실 알림판에서도 똑같이 Smalltalk 의 글토막을 넣을 수 있고, 넣은 글토막을 가늠하고 펴보고 탐색할 수 있습니다. 즉 일터에서 제공하는 기본적인 글토막 가늠 기능을 그대로 제공하는 것입니다. 그러면 도대체 왜 "알림판" 이 Smalltalk 환경에 존재해야 하는 것일까요?


이 알림판은 Smalltalk 환경이 사용자에게 알려야 할 사항이 있으면 그것을 정리하여 출력해 주는 중요한 곳입니다. 이를테면 Smalltalk로 작업하는 도중에 기억공간이 모자란다거나, 갈래나 길수, 또는 기타 여러 가지 객체들이 들어있는 꾸러미(package)를 읽어들였다던가 하는 정보가 모두 이 알림판에 나타납니다. 그러므로 사용자는 늘 알림판을 주시하고, 여기에 기록된 사항들이 있으면 제대로 살피고 넘어가는 습관을 기르는 것이 좋습니다.


알림판이 이렇게 Smalltalk 환경과 밀접한 관계를 가지고 있기 때문에, 보통 우리들이 프로그래밍을 하거나 작업을 할 때에는 일터에서 하는 것이 좋습니다. 관례적으로 알림판에서는 Smalltalk 환경에 전체적으로 영향을 줄 수 있는 글토막을 기록하고 가늠하는 것이 좋습니다.


그런데 이 알림판은 Smalltalk 시스템만 사용할 수 있는 것은 아닙니다. 바로 여러분들이 이 알림판에 직접 글을 쓸 수 있습니다. 물론 글쇠판을 이용해서 쓰는 것이 아니라, 알림판이 알아들을 수 있는 지시를 통하여 글을 쓰는 것입니다. 이제 알림판에 글을 쓰기 위하여, 도대체 알림판이 어떤 지시를 알아들을 수 있는지 살펴봅시다. Smalltalk의 모든 것은 객체입니다!


알림판 객체 - Transcript

객체에게 지시를 보내기 위해서는 그 객체를 불러야 합니다. 이를테면 "3 + 4" 라고 하는 지시를 내려야 할 때에는 "3"이라는 객체를 불러야 하고, 10의 계승을 구하기 위해서는 "10"을 명시해 주어야 합니다.

3 + 4            7
10 factorial     3628800


마찬가지로 알림판에 어떤 지시를 내리기 위해서는 우선 알림판 객체를 글토막에 명시해야 합니다.


Smalltalk 에서 알림판은 "Transcript"라고 부릅니다. 우리가 정수 갈래를 Integer라고 불렀고, 글줄 갈래를 String 이라고 부른 것처럼, 알림판 역시 Transcript 라고 부르면 되는 것입니다.


아래와 같이 일터에 Transcript 라고 쓴 후 이 글토막을 펴 봅시다. (Smalltalk에서 낱말을 쓸 때 대문자와 소문자를 가려야 한다는 것은 앞에서 말한 바 있습니다.)

Transcript       a TranscriptShell


위와 같이 "Transcript", 곧 알림판을 펴 보면 "a TranscriptShell" 이라는 결과를 얻을 수 있습니다. 알림판의 껍데기(transcript shell)라는 조금은 묘한 결과가 나왔습니다. 아무튼 여기서 중요한 것은 Transcript 역시 Smalltalk에서 하나의 객체라는 것입니다.


그럼 이번에는 "Transcript"를 탐색해 봅시다. 그러면 아래 그림 3-2 와 같은 화면을 볼 수 있을 것입니다.


그림 3-2::"a TranscriptShell"을 탐색하는 모습(Stp3-2.gif)


보시다시피 Transcript는 여러 개의 성분을 가지고 있는 핫객체입니다. 물론 여러분 중에 Transcript를 구성하고 있는 성분의 의미를 아직까지 제대로 이해하는 분이 드물겠지만, 아무튼 알림판에 지시를 내리기 위해서는 Transcript를 사용해야 한다는 것만 기억해 두면 되겠습니다.


알림판이 알아듣는 지시

알림판(Transcript) 역시 Smalltalk에서 하나의 객체입니다. 따라서 알림판에 글씨를 쓰기 위해서는 도대체 이 알림판이 어떤 지시를 알아들을 수 있는지를 먼저 살펴보아야 합니다. 알림판이 알고 있는 길수들은 아래의 네 가지입니다.


  • Transcript nextPut: aCharacter
    • aCharacter 글자를 알림판에 써 낸다.
  • Transcript nextPutAll: aString
    • aString 글줄을 알림판에 써 낸다.
  • Tanscript space
    • 알림판에 빈 칸 한 개를 써 낸다.
  • Transcript cr
    • 알림판에서 줄을 바꾼다.


우선 알림판에 지시를 내리기 전에 깨끗한 새 일터를 만듭시다. File > New 메뉴를 선택하면 새 일터가 열릴 것입니다. 새로운 마당을 시작할 때 우선 새 일터를 열어놓고 시작하는 것이 좋습니다. 그런 다음 방금 연 일터와 알림판을 그림 3-1 처럼 동시에 볼 수 있도록 위치를 맞춥시다. 이렇게 하면 일터에서 내린 지시가 바로 알림판에 전달되어 반응하는 것을 볼 수 있습니다. 그런 다음 알림판에 있는 모든 내용을 깨끗이 지웁시다.


이제 일터에서 다음 글토막을 입력해 봅시다.

Transcript nextPutAll: '안녕하세요?'.
Transcript nextPutAll: '저는 지금 알림판에 글을 씁니다.'.


위의 글토막은 두 개의 Smalltalk 명령이 들어 있습니다. 따라서 위의 글토막을 가늠하는 방법도 두 가지입니다.


먼저 한 줄씩 글토막을 가늠하는 방법입니다. 이 경우에는 가늠하고 싶은 명령이 들어 있는 줄에 커서를 위치한 다음 가늠하거나Ctrl-E, 펴거나Ctrl-D 탐색할 수Ctrl-I 있습니다.


두 번째 경우는 위의 글토막 전체에 덩이를 씌워서 한꺼번에 가늠하거나 펴거나 탐색하는 방법이 있습니다. 아래 그림 3-3 을 살펴보면 두 개의 명령에 덩이를 씌운 것을 볼 수 있습니다.


그림 3-3::일터에서 글토막에 덩이를 씌운 모습 (Stp3-3.gif)


그런데 여기서 두 개 이상의 명령을 한꺼번에 실행하기 위해서는 각각의 명령을 마침표(.)로 나누어주어야 합니다. Smalltalk에서는


명령과 명령 사이를 구분하기 위해서 마침표를 사용한다


는 대(大) 명제가 있습니다. 만약 한 개의 명령이 들어있는 경우라면 굳이 명령어의 끝에 마침표를 둘 이유가 없지만, 그림 3-3 처럼 두 개 이상의 명령들로 이루어진 글토막의 경우는 위의 명령과 아래 명령을 구분하기 위해서 반드시 마침표가 필요한 것입니다.


대개의 경우 한 줄씩 글토막을 가늠하는 것이나 덩이를 씌워서 가늠하는 것이나 별반 다를 것은 없지만, 어떤 경우에는 반드시 덩이를 씌워서 여러 개의 명령을 한번에 실행해야 하는 경우도 있습니다. 이는 상황에 따라 적절하게 가려서 사용하면 될 것입니다.


아무튼 우리의 경우는 덩이를 씌워서 가늠해 보겠습니다. 그림 3-3 처럼 두 줄의 글토막에 덩이를 씌운 후 Ctrl-E 글쇠를 눌러서 글토막을 가늠해 보십시오. 그러면 알림판에 그림 3-4 와 같이 글줄이 써져 있음을 발견하게 될 것입니다.


그림 3-4::일터에서 내린 명령으로 알림판에 써 진 글 (Stp3-4.gif)


글토막을 가늠하자마자 일터에 다음과 같이 글줄이 써져 있을 것입니다.

안녕하세요?저는 지금 알림판에 글을 씁니다.


그런데 조금 이상한 것이 있습니다. 우리는 분명히 두 개의 명령을 내렸지만, 알림판을 보면 한 줄에 글줄이 쓰여졌습니다. 왜 그런 것일까요?


여기서 사용하게된 #nextPutAll: 지시는 알림판에 글줄을 쓰기는 하지만 줄을 바꾸어주지는 않습니다. 결국 알림판에서 줄을 바꾸기 위해서는 우리가 스스로 알림판에게 줄을 바꾸라는 지시를 내려주어야 합니다. 아래를 보십시오.


바탕글 1::알림판에 글을 쓰는 프로그램

Transcript nextPutAll: '안녕하세요?'.
Transcript cr.
Transcript nextPutAll: '저는 지금 알림판에 글을 씁니다.'.


위의 바탕글을 가늠해 봅시다. 이제부터 위와 같은 모양의 바탕글이 나오면. 이는 한꺼번에 가늠해야 함을 나타냅니다. 위와 같이 되어 있다면 이제부터는 한꺼번에 덩이를 씌워서 가늠하십시오.


위의 바탕글을 가늠한 다음 알림판을 보면, 과연 두 문장 사이에 줄이 바뀌어 있다는 것을 알게 됩니다. 바로 #cr 이라는 지시를 Transcript 객체에게 내려주었기 때문입니다. 여기서의 cr 은 Carriage Return을 말하는 것으로, 줄을 바꾸어라는 뜻입니다.


Transcript 객체가 알아듣는 지시로 space가 있다고 했습니다. 그러면 위의 바탕글 1 을 아래처럼 고쳐서 가늠해 봅시다.


바탕글 2::#space 지시를 알아듣는 알림판

Transcript nextPutAll: '안녕하세요?'.
Transcript space.
Transcript nextPutAll: '저는 지금 알림판에 글을 씁니다.'.


위의 글토막을 한꺼번에 펴 보면 알림판에 아래와 같은 결과가 나타날 것입니다.


안녕하세요? 저는 지금 알림판에 글을 씁니다.


물음표(?)와 "저" 사이에 한 칸의 빈칸이 생겼습니다. 이렇게 #space 지시를 내려주면 한 칸의 빈칸을 만들어 냅니다.


Transciprt 는 #nextPut: 이라는 지시도 알아듣습니다. 이 지시는 지금까지 우리가 사용헀던 #nextPutAll: 과는 달라서 한 개의 글자만을 인자로 받아들입니다. 아래의 바탕글 3 을 펴 봅시다.


바탕글 3::#nextPut:의 시험

Transcript nextPut: $A.
Transcript nextPut: $B.
Transcript nextPut: $C.


'A', 'B', 'C'라는 세 개의 글자가 다닥다닥 붙어서 나타났습니다. 그러니까 nextPut: 은 한 개의 글자를 알림판에 써내는 일을 합니다. 'A'와 $A는 엄연히 다른 객체라는 것을 벌써 잊지는 않으시겠지요? 이 두 개의 객체가 다른지 같은지 잘 모르겠다면 여러분이 직접 물어보십시오. 'A'와 $A에게 각각 #class 라는 지시를 내려주면 확실히 두 객체가 속한 갈래가 다르다는 것을 알 수 있을 것입니다.


그러면 여기서 잠깐 생각해 볼 것이 있습니다.

Transcript nextPutAll: 'A'.      (1)
Transcript nextPut: $A.          (2)


위의 두 개의 문장의 실행 결과는 같을까요, 다를까요? 결론부터 말씀드리면 똑같습니다. 그러면 #nextPut:이란 길수가 왜 필요한 것일까요? 그것은 바로 #nextPutAll:보다 #nextPut:이 훨씬 적은 일을 하기 때문입니다. 즉 똑같은 결과를 얻어내는데 있어서 #nextPutAll: 은 #nextPut: 보다 컴퓨터에게 많은 일을 시켜야 하고, 결과적으로 성능이 떨어진다는 것입니다. 그래서 글자 한 개를 나타내기 위해서는 #nextPutAll:보다는 #nextPut:을 사용하도록 권합니다. 파리 한 마리를 잡는다고 핵폭탄을 쓸 수는 없지 않겠습니까?(임인건, 1992)


알림판에 객체 써내기

지금까지 우리는 알림판에 간단한 문장을 써 보았습니다. 이제 조금 복잡한 문장을 써 보도록 합시다. 아래와 같은 문장을 알림판에 쓰려면 어떻게 해야 할까요?

지금부터 재미있는 '계산'을 해 봅시다.
1 + 2는 3입니다.
2의 10제곱은 ****입니다.
10의 계승은 얼마일까요? 바로 *******입니다.
'abc'는 $a $b $c로 이루어져 있고, 길이는 3 입니다.


그럼 위의 내용을 알림판에 써냅시다. 일단 그 전에 지금까지 알림판에 들어있던 모든 글자들을 지웁시다. (※ 쉽게 지우려면 Ctrl-A (전체선택)을 한 다음 -Delete글쇠를 누르면 됩니다.)


처음 문장을 봅시다.

지금부터 재미있는 '계산'을 해 봅시다.


자, 글줄 안에 작은따옴표가 들어가 있습니다. 어떻게 하면 좋을까요? 앞서 글줄에 대해서 공부할 때 글줄 속에 작은따옴표를 넣는 방법을 공부한 바 있습니다. 기억나십니까? 아래와 같이 하면 됩니다.

Transcript nextPutAll: '지금부터 재미있는 ''계산''을 해 봅시다.'
Transcript cr.


다음 문장은 어떻게 써야 하겠습니까?

1 + 2는 3입니다.


간단합니다.

Transcript nextPutAll: '1 + 2는 3입니다.'.


위와 같이 하면 될 것입니다. 그런데... 다음과 같은 경우는 어떻게 해야하겠습니까?

2의 10제곱은 ****입니다.


간단한 계산식 같으면 우리가 답을 직접 써넣을 수 있지만, 이와 같은 경우는 조금 어려운 상황입니다. 어떻게 해야할까요? 일터에서

2 ** 10   1024


와 같이 글토막을 만들어서 결과를 얻고, 그 결과를 그대로 이용해서

Transcript nextPutAll: '2의 10제곱은 1024입니다.'.
Transcript cr.


이라고 할 수도 있습니다. 물론 그렇게 해도 괜찮겠지만, 만약 2 의 20 제곱이라던가 30 제곱처럼 값이 바뀌게 되면, 글토막을 새로 만들어서 결과를 얻고, 그 결과를 이용해서 다시 알림판으로 결과를 보내야 합니다. 이것은 너무나 비효율적이지요? 만약 어떤 식을 계산한 결과를 바로 알림판으로 써 낼 수 있다면 매우 편리할 것입니다.


물론! 방법이 있습니다. 모든 객체는 자신이 화면에 표시되는 모양 그대로 알림판에 내용을 써 낼 수 있습니다. 즉 1 이나 3 등과 같은 정수는 물론 'abc' 등과 같은 글줄 역시 그대로 써 낼 수 있습니다. 아래의 글토막을 펴 보십시오.

3 printOn: Transcript.           3 (알림판에)


3이라는 객체에게 #printOn: 이라는 지시를 내렸습니다. 그랬더니 알림판에 '3'이 쓰여졌습니다. 이렇게 어떤 객체에게 #printOn: 지시를 내리면 그 객체를 그대로 #printOn:에 전달된 인자로 그 내용을 써냅니다. 이 경우에는 알림판(Transcript)을 지정했으므로 당연히 '3'이라는 객체가 알림판에 써넣어진 것입니다. 그러면 다음과 같이 해 봅시다.

$a printOn: Transciprt           $a (알림판에)


알림판에 "3$a"라고 적혀 있을 것입니다. 여기서 앞의 '3' 은 객체 3 에게 #printOn: 지시를 내렸을 때의 값이고, 바로 뒤이어 나온 '$a'가 실제로 우리가 내린 명령의 결과가 됩니다. 즉 $a 객체에게 printOn: 지시를 내리면 그 객체 모양 그대로의 모습으로 알림판에 나타납니다. 물론 여기서도 알림판에게 #cr 지시를 내리기 전까지는 줄을 바꾸지는 않습니다.


이것을 이용하면 앞에서 이야기헀던 계산식의 결과를 그대로 알림판으로 내보낼 수 있습니다. 아주 간단합니다. 아래와 같이 시험해 보십시오. 일단 아래 글토막을 가늠하기 전에 "3$a"라고 적혀있는 부분을 지워서 깨끗이 만듭시다.

(2 ** 10) printOn: Transcript    1024 (알림판에)


알림판에 2 의 10 제곱을 계산한 결과가 그대로 나왔습니다. 위에서 왜 2의 10제곱을 구하는 부분에 괄호를 둘렀을까요? 물론 이 경우 길표의 우선순위만 놓고 보면 쇠마디(keyword) 지시인 #printOn:보다는 겹마디(binary) 지시인 #**이 먼저 가늠되기 때문에 괄호가 필요하지 않습니다. 그러나 위와 같이 괄호를 둘러주면 바탕글을 보다 쉽게 알아볼 수 있습니다.


자, 이렇게 해서 객체를 알림판에 그대로 써 내는 방법을 알아보았습니다. 방금 알림판에 찍힌 "1024"를 지우고, 이제 본격적으로 우리가 쓰려고 헀던 문장을 써 봅시다.


바탕글 4

Transcript nextPutAll: '2의 10제곱은 '.
(2 ** 10) printOn: Transcript.
Transcript nextPutAll: '입니다.'.
Transcript cr.


그렇다면 아래의 문장도 똑같은 방법을 이용하여 쓸 수 있습니다.

10의 계승은 얼마일까요? 바로 ***** 입니다.


바탕글 5

Transcript nextPutAll: '10의 계승은 얼마일까요? 바로 '.
(10 factorial) printOn: Transcript.
Transcript nextPutAll: '입니다.'.
Transcript cr.


여기서 사용한 #printOn: 지시를 알아들을 수 있는 객체는 숫자만이 아닙니다. 이제

'abc'는 $a, $b, $c로 이루어져 있고, 길이는 3입니다.


를 써낼 차례입니다. 어떻게 하면 가장 효율적으로 바탕글을 만들 수 있을까요? 여러분이 생각한 것과 제가 쓴 것이 어떻게 다른지 한 번 비교해 보십시오.


바탕글 6

'abc' printOn: Transcript.
Transcript nextPutAll: '는 '.
$a printOn: Transcript. Transcript nextPutAll: ', '.
$b printOn: Transcript. Transcript nextPutAll: ', '.
$c printOn: Transcript. 
Transcript nextPutAll: '로 이루어져 있고, 길이는 '.
('abc' size) printOn: Transcript.
Transcript nextPutAll: ' 입니다.'.
Transcript cr.


Smalltalk에서는 명령과 명령을 마침표로 구분하기 때무에, 위와 같이 두 개의 명령을 한 줄에 써넣을 수도 있습니다. 물론 이 때에는 바탕글이 읽기 어려워질 수 있기 때문에 신중하게 생각해서 글을 써야 합니다.


그럼 이제까지 쓴 것을 한 데 모아서 아래에 적어보겠습니다.


바탕글 7::알림판에 문장과 객체 써내기

Transcript nextPutAll: '지금부터 재미있는 ''계산''을 해 봅시다.'
Transcript cr.

Transcript nextPutAll: '1 + 2는 3입니다.'.

Transcript nextPutAll: '2의 10제곱은 '.
(2 ** 10) printOn: Transcript.
Transcript nextPutAll: '입니다.'.
Transcript cr.

Transcript nextPutAll: '10의 계상은 얼마일까요? 바로 '.
(10 factorial) printOn: Transcript.
Transcript nextPutAll: '입니다.'.
Transcript cr.

'abc' printOn: Transcript.
Transcript nextPutAll: '는 '.
$a printOn: Transcript. Transcript nextPutAll: ', '.
$b printOn: Transcript. Transcript nextPutAll: ', '.
$c printOn: Transcript. 
Transcript nextPutAll: '로 이루어져 있고, 길이는 '.
('abc' size) printOn: Transcript.
Transcript nextPutAll: ' 입니다.'.
Transcript cr.


우리가 본 것 중에 가장 긴 바탕글이 되었습니다. 물론 필자가 쓴 것이 가장 좋다는 말은 아닙니다. 여러분 나름대로 좀 더 간결하게 다른 방법으로 바탕글을 만들 수 있습니다. 글을 많이 써 보면 좀 더 다듬어진 바탕글을 만들어낼 수 있을 것입니다.


아무튼 이렇게 알림판을 나타내는 Transcript 객체에게 #nextPutAll:, #nextPut:, #cr, #space 라는 네 개의 지시를 이용함으로써 알림판에 전혀 손을 데지 않고 글줄을 써 낼 수 있습니다. 아울러 모든 객체는 #printOn: 지시를 알아듣기 때문에, 이 지시를 이용해서 알림판에 객체의 내용을 표시할 수도 있었습니다.


그러면 여기서 다음의 두 글토막을 살펴봅시다.

Transcript nextPutAll: 'I am a boy.'.    (1)
'I am a boy.' printOn: Transcript.       (2)


위의 두 글토막은 똑같은 일을 할까요, 다른 일을 할까요?


실제로 직접 위의 글토막을 가늠해 보면 서로 다른 결과를 얻게 됨을 알 수 있습니다. (1)의 경우는

I am a boy.


가 알림판에 나타나고, (2)의 경우에는

'I am a boy.'


가 알림판에 나타납니다. 즉 #nextPutAll: 은 글줄을 구분하기 위해서 사용하는 작은따옴표를 나타내지 않지만, #printOn:의 경우는 객체가 나타나는 모습 그대로를 표시하기 때문에 작은따옴표까지 포함해서 나타내게 됩니다. 마찬가지로

Transcript nextPut: $a.          (1)
$a printOn: Transcript.          (2)


의 경우를 살펴봅시다. (1)의 경우는 당연히

A


가 나타날 것이고, (2)의 경우는

$A


가 나타납니다. 즉 #nextPut:의 경우는 글자 한 개만 나타내지만, #printOn: 의 경우는 글자임을 나타내는 $표시까지 함께 알림판에 나타냅니다.


따라서 주로 알림판에 사용자에게 알려줄 글을 쓸 때에는 #nextPutAll: 이나 #nextPut: 을 사용하고, 객체의 내용을 나타내 줄 때에는 #printOn:을 사용하면 좋습니다.


#printOn: 과 한글


필자가 실험을 하다가 한 가지 놀라운 사실을 발견했습니다. 아래의 글토막을 가늠해 봅시다.

'우리 나라' printOn: Transcript.


알림판을 살펴보면 달랑 두 개의 작은따옴표만 있고 정작 그 속에 들어가야 할 한글은 나타나지 않습니다. 더욱 더 재미있는 것은

'1과 3은 홀수' printOn: Transcript.


위의 글토막을 펴보면 달랑 '1 3'이라는 결과만 나타납니다. 즉 한글을 무시해 버립니다. 도대체 어떻게 된 것인지... 다행이 사용자가 읽어야 할 글을 출력할 때에는 #nextPutAll:을 사용하고 있기는 하지만, 뭔가 문제가 있습니다. 필자가 이 글을 끝내는 즉시 Object Arts사에 문의를 해 볼 생각입니다. 아무튼 뭔가 좀 찜찜하다는 생각을 감출 수가 없습니다.


잇따름 지시

Smalltalk로 프로그래밍을 하거나 바탕글을 만들 때 하나의 객체에게 여러 개의 지시를 연속적으로 내려야 하는 경우가 있습니다. 그런데 이렇게 객체에게 지시를 내릴 때마다 일일이 지시를 받는 객체(receiver)의 이름을 써 준다는 것은 어찌 보면 상당히 귀찮은 일이라 할 수 있습니다. 아래 바탕글을 봅시다.


바탕글 8::한 객체에게 여러 개 지시 내리기

Transcript nextPutAll: '우리는 민족 중흥의 역사적 사명을 띄고 '.
Transcript nextPutAll: '이 땅에 태어났다. '.
Transcript cr.
Transcript cr.
Transcript nextPutAll: '이상은 국민교육헌장의 첫머리다.'.
Transcript cr.


그냥 보기만 해도 상당히 짜증나는 바탕글이 아닐 수 없습니다. 더구나 위의 바탕글을 글쇠판으로 치려고 하면 더욱 더 힘이 듭니다. 위와 같은 경우에 바로 잇따름 지시(cascaded message)를 사용하면 매우 편리합니다.


보통 글토막에서 하나의 명령이 끝나면 마침표를 찍어주고, 그 뒤에 계속해서 지시를 받을 객체의 이름과 지시의 내용을 적어줍니다. 그러나 잇따름 지시는 명령문의 끝에 마침표를 찍지 않고 쌍반점(;)을 찍습니다. 그리고 연이어서 지시를 받을 객체의 이름을 적는 대신 바로 지시를 적습니다. 이렇게 하면 쌍반점 다음에 씌여진 지시는 바로 앞의 명령에서 지시를 받은 객체에게 그대로 전달됩니다. 말이 어렵지 실제로 보기를 보면 그렇지 않습니다. 위의 바탕글 8 을 잇따름 지시를 사용해서 다시 써 보면 아래와 같습니다.


바탕글 9::잇따름 지시를 사용한 바탕글

Transcript 
        nextPutAll: '우리는 민족 중흥의 역사적 사명을 띄고 ';
        nextPutAll: '이 땅에 태어났다. '; cr; cr;
        nextPutAll: '이상은 국민교육헌장의 첫머리다.'; cr.


여기서 두 번째 #nextPutAll: 로부터 #cr 등의 모든 지시는 첫 번째 명시된 Transcript 객체에게 잇따라 보내지게 됩니다. 이렇게 하면 바탕글을 훨씬 깔끔하게 작성할 수 있게 됩니다.


그런데 이와 같은 잇따름 지시는 잘못 쓰게 되면 바탕글을 매우 혼란하게 만듭니다. 다음 글토막을 가늠해서 펴 보면 어떤 결과가 나오곘습니까?

3 + 2 - 1; * 100         ?


400 이 될까요, 500 이 될까요? 실제로 일터에서 위의 글토막을 입력하고 펴 보면 500을 값으로 내놓게 됩니다. 위에서 일단 정수 "3"에 "+ 2" 지시가 보내지고 5라는 결과를 얻습니다. 그런 다음 이 "5"에 "- 1" 지시가 내려지는데, 여기서 잇따름 지시인 "* 100"이 함께 내려집니다. 즉 "- 1"의 경우 "5" 로 보내지기는 하지만 실제로 계산된 결과인 4는 무시해 버리고 마침내 "5" 에 "* 100"이 보내지게 되어 "500"이라는 결과값을 얻게 됩니다.


실제로 프로그램을 작성할 때에는 이렇게 애매한 바탕글을 작성하면 곤란합니다. 그렇지만 잇따름 지시를 잘만 이용하면 바탕글을 훨씬 깔끔하고 읽기 쉽게 만들 수 있습니다.


바탕글의 기독성


프로그램을 작성하기 위해서는 일단 바탕글(source code)를 써야 합니다. 바탕글에는 컴퓨터에게 내릴 여러 가지 명령문들이 들어갑니다. 그런데 바탕글은 한 번 쓰여지고 여러 번 읽혀지기 때문에 바탕글을 읽기 좋게 쓰는 것은 매우 중요합니다. 만약 바탕글을 아무렇게나 쓰면 한참 후에 다시 바탕글을 읽어서 프로그램의 흐름을 분석하려고 할 때 매우 곤란을 겪게 됩니다. Dolphin Smalltalk 에서 바탕글의 글토막에 문법 돋이(syntax highlighting)를 적용하는 것도 바로 이런 이유 때문입니다.


바탕글은 지나치게 길어져서는 안됩니다. 특히 Smalltalk 처럼 객체지향 언어의 경우는 바탕글을 매우 짧게 쓰더라도 충분히 프로그램을 작성할 수 있기 때문에 지나치게 바탕글이 길면 곤란합니다.


또한 바탕글을 쓸 때에는 적당하게 들여 쓰기(indentation)를 해야 합니다. 바탕글 9 를 보면, Transcript 아래에 있는 모든 문장 앞에 빈칸이 들어가 있음을 볼 수 있습니다. 이는 잇따름 지시의 특성상 모든 지시들이 하나의 객체에게 잇따라 전해지는 것을 쉽게 표현한 것입니다. 이와 함꼐 앞으로 이렇게 여러 가지의 들여 쓰기 방법들이 소개될 것입니다.


또한 바탕글이 길어질 경우, 바탕글을 몇 개의 작은 부분으로 나누고, 각 부분마다 빈줄을 한 줄 정도 삽입해 두면 훨씬 읽기가 편합니다. 필자도 글을 쓸 때 여러분이 쉽게 읽을 수 있도록 가끔 빈 줄을 삽입하듯이, 여러분이 바탕글을 쓸 때에도 프로그램이 하는 일을 보고 적당히 빈줄을 넣어 주면 훨씬 읽기 좋은 바탕글이 됩니다.


다시 한 번 말씀드리지만, 바탕글을 한 번 쓰여지고 여러 번 읽혀지기 때문에 조금 번거롭더라도 바탕글의 기독성(readability)을 높이기 위해서 들여 쓰기와 여러 가지 표현 방법들을 잘 활용해야 할 것입니다.


지금까지 우리는 알림판에 대해서 알아보았습니다. 이 알림판은 Smalltalk 환경이 여러분에게 중요한 사실을 알려줄 때 이용하는 창구의 역할을 수행할 뿐만 아니라, 여러분이 직접 프로그램에서 필요한 문장을 적어줄 수도 있다는 것을 공부했습니다. 아울러 모든 객체는 #printOn: 지시를 알아듣는다는 것도 알아보았습니다. 또한 하나의 객체에게 두 개 이상의 지시를 잇따라 내려야 할 때 일일이 지시를 받는 객체(receiver)의 이름을 적어주는 것보다는 잇따름 지시를 이용하는 것이 훨씬 편리하다는 것도 알아보았습니다.


다음에는 프로그래밍에서 매우 중요하게 다루어지게 될 꼬리표(variable)에 대해서 알아보도록 하겠습니다. 이 꼬리표를 사용하면 비로소 Smalltalk 에 의미 없이 존재하는 수많은 객체에게 여러분 나름대로의 이름을 붙여줄 수 있습니다.


꼬리표 인생

지금까지 우리는 셀 수 없을 정도로 많은 객체에게 여러 가지 지시를 내려보았습니다. 또한 이렇게 해서 역시 셀 수 없는 많은 객체들을 결과물로 되돌려 받았습니다. 그러나 이런 객체들은 대부분 자신이 맡은 일을 수행하고는 그냥 그대로 사라져 가고 말았습니다.

3 + 4   7


위와 같은 아주 간단한 글토막에서도 "3", "4", "7"이라는 객체는 그냥 한 번 저렇게 쓰여지고는 어디론가 사라져 가버린 것입니다. 그것은 저런 객체들이 오래 두고 보관하기에는 별다른 가치가 없기 때문입니다.


그러나 어떤 객체는 우리에게 중요한 의미로 다가옵니다. 복잡한 계산이나 여러 개의 명령을 통해서 얻어낸 객체라면 그것은 나름대로 큰 의미를 가지고 있습니다. 이렇게 Smalltalk에는 많은 객체가 존재하지만, 그 중에서 우리에게 특별하게 의미가 있는 객체들이 있고, 그렇지 않은 객체들도 있는 것입니다.


이제 우리는 여기서 이렇게 나름대로 의미를 가지고 있는 객체들에게 이름을 부여하는 방법을 배우게 됩니다. 수많은 객체 중에 어느 하나에 나만의 이름을 붙여준다면, 이제 그 객체는 적어도 나에게는 매우 큰 의미를 가지는 것입니다.


이름이 뭐지?

필자는 어릴 적에 어머니를 따라서 길을 가다보면 "이름이 뭐니?" 하고 물으시는 어른들을 자주 뵈었었습니다. "김찬홍", 이는 누구와도 다른 저만의 이름입니다. 그 누구도 제 이름을 바꿀 수 없고, 또한 "김찬홍"이라고 하는 그 이름의 의미를 바꿀 수는 없습니다. 왜냐하면 "김찬홍"은 지금 글을 쓰고 있는 바로 "저 자신"이기 때문입니다. 이름은 그만큼 중요한 것입니다.


그런데 이런 저에게도 단지 "김찬홍"이라는 이름만 있는 것이 아닙니다. 집에서 어머님은 저를 "막내야"라 부르십니다. 학교에 오면 "선배님"이란 소리도 듣습니다. 길을 가다보면 "아저씨"라는 소리도 듣게 됩니다. 좀 기분이 나쁘기는 하지만, 아저씨는 아저씨이니 별 수 없이 "아저씨" 소리를 듣고도 다른 말을 하기가 어렵습니다. :) 그 뿐인가요? 성당 주일학교에 가면 여지없이 나는 "선생님"으로 통합니다. 그러고 보니 성당에서 신부님은 저를 세례명으로 "안드레아"라고도 부르십니다.


보십시오. "김찬홍" 이라는 한 사람에게도 "막내", "선배", "아저씨", "선생님", "안드레아" 라는 여러 가지의 이름이 있습니다. 제가 "안드레아" 라고 불린다고 해서 "찬홍"이 아니겠습니까? 그것은 아니겠지요? 이런 모든 이름들은 단 한 사람, 바로 저를 부르기 위해서 쓰인 것입니다.


사람들이 이름을 쓰는 이유는 무엇일까요? 그것은 이름이 가리키는 대상을 부르기 위함입니다. "찬홍아!", "막내야!", "안드레아야!"라고 부르는 것은 바로 저를 부르기 위해서입니다. 그래서 사람들은 자기 이름이 불리면 가장 큰 관심을 갖고 지켜보게 됩니다.


그 뿐 아니라 세상에 있는 모든 사물에는 저마다의 이름이 있습니다. 이는 사람들이 모두 그 사물을 가리켜 일컫기 위함입니다. 심지어는 길에 굴러다니는 쓸모 없는 것들도 "쓰레기"라는 이름을 가지고 있습니다. 물론 우리가 부르고 싶은 사물의 중요도가 올라가면 이름도 훨씬 구체적으로 부르게 됩니다. 그냥 "종이"가 아니라 "일기장"이라고 부르듯이 말입니다.


Smalltalk 에서도 마찬가지입니다. Smalltalk 는 하나의 작은 세상입니다. 이 세상에는 헤아릴 수 없이 많은 객체가 존재합니다. 그리고 Smalltalk 환경에 들어와 있는 사람(프로그래머)이 이런 객체들에게 어떤 지시를 내리고 싶을 때에는 먼저 그 객체의 이름을 불러주어야 합니다. 따라서 Smalltalk의 모든 객체에는 저마다의 이름이 부여되어 있습니다.


바로값과 꼬리표

Smalltalk에서 쓰이는 이름에는 두 가지 종류가 있습니다. 그 하나가 "바로값"(literal)입니다. 이 바로값은 우리가 앞의 2.3.5 마디(#20)에서 다루어 본 적이 있는 객체입니다. 그러나 "바로값이 무엇인가?"하고 물었을 때 시원하게 대답할 수 있는 정의를 내리지는 않았습니다. 그것은 이 바로값과 객체에게 붙은 이름이 관련되어있기 때문입니다.


"어떤 객체가 변하지 않는 이름을 가지고 있다면 그 객체는 바로값이다."


이 말을 바꾸어 말하면


"이름으로 __바로__ 객체를 지칭할 수 있다면 이 객체는 바로값이다."


라고 말할 수도 있습니다. 즉 바로값은 고유의 이름을 가진 객체들을 가리키는 말입니다.

$a 글자 a를 나타내는 이름.
'Im' 세 개의 글자($I, $', $m)로 이루어진 글줄에 붙는 이름.
#red $r, $e, $d 세 개의 글자로 이루어진 이름값
2000 정수 "이 천"을 나타내는 이름
1.2e2 소수 "1200.0"을 나타내는 이름
true "참"을 나타내는 이름
false "거짓"을 나타내는 이름
nil "헛"(아무 의미 없음)을 나타내는 이름
#(1 2) 정수 1과 2가 들어 있는 배열을 나타내는 이름.


보다시피 바로값은 객체에게 따로 이름을 붙일 필요가 없이 바로 나타낼 수 있습니다. 이렇게 일정한 규칙에 따라서 글자나 숫자를 늘어놓게 되면 바로값을 나타내는 이름이 되는 것입니다. 즉, 이런 바로값의 이름은 __결코__ 변하지 않습니다. "3"은 언제까지나 "3"인 것입니다.


Smalltalk 에서 쓰이는 이름 가운데 두 번째가 바로 "꼬리표"(variables)입니다. 우리가 어떤 물건에 이름을 붙이고 싶을 때 꼬리표(딱지, sticker)에 이름을 써 붙이듯이, 객체에게 우리 나름대로 이름을 지어주고 싶을 때에도 꼬리표를 붙여주면 됩니다. 바로값을 가리키는 객체의 이름은 어떠한 일이 있어도 바뀔 수 없지만, 꼬리표가 붙은 객체는 다릅니다. 필요하면 언제든지 다른 꼬리표를 붙여서 객체의 이름을 다르게 부를 수 있습니다.


바로값에 나름대로의 규칙이 있듯이 꼬리표를 쓰는 데에도 나름대로의 규칙이 있습니다. "2.3.5." 마디(#19)에서 우리는 이미 "이름값"(symbol)에 대해서 공부한 적이 있습니다. 바로 이 이름값이 꼬리표를 쓰는데 사용됩니다. 이름값을 어떻게 만드는지 벌써 잊어버리셨습니까? 여러분을 위해서 이름 붙이는 방법에 대해서 다시 한 번 정리해 드리겠습니다. 중요한 내용이니깐 꼭 이해하고 넘어가시기 바랍니다.


이름 짓기 규칙


  1. 이름에는 기본적으로 영문 알파벳과 숫자, 그리고 밑줄(_)을 사용한다.
  2. 첫 글자가 숫자여서는 안 된다.
  3. 영문 알파벳의 경우는 대문자와 소문자가 엄격하게 구별된다.
  4. 겹마디(binary) 지시에 한해서 다음의 기호를 사용할 수 있다.
    ! @ % * ( [ { } < > = | \ / ? + - :
    


꼬리표는 다시 큰 꼬리표(global variables)와 작은 꼬리표(local variable)로 나누어집니다. 큰 꼬리표의 이름은 대문자로 시작하고, 작은 꼬리표의 이름은 소문자로 시작합니다. 우리는 벌써 몇 개의 큰 꼬리표를 사용해 왔습니다. 믿어지지 않는다고요? 다음을 보십시오.

Integer superclass                       Number
#red isKindOf: String                    true
Transcript nextPutAll: '하하하!'. 
(10 factorial) printOn: Transcript.


위에서 "Integer", "String", "Transcript"는 모두 큰 꼬리표입니다. 자세히 보면 모두 꼬리표 이름의 첫 글자가 대문자로 시작하고 있음을 알 수 있습니다. 놀라셨습니까? 그렇지만 엄연히 위의 세 개는 꼬리표입니다. 여태껏 우리가 자주 보아왔던 Integer, String, Object 등은 물론이고 Smalltalk에 존재하는 모든 갈래(class)들은 결국 큰 꼬리표가 붙어 있었던 것입니다.


꼬리표의 값이 바뀔 수 있다고 앞에서 이야기한 적이 있는데, 당연히 이런 갈래에 붙어 있는 큰 꼬리표 역시 다른 객체에 붙일 수 있습니다. 즉 String 이 지금은 "String 갈래" 에 붙어 있지만, 마음만 먹으면 언제든지 이 꼬리표를 떼어다가 "3"이나 $a 처럼 하찮은 객체에게 붙여버릴 수도 있다는 말입니다! Transcript도 마찬가지입니다. Transcript를 일터에서 펴 보면

Transcript       a TranscriptShell


이 뜨는데, 결국 이 Transcript 꼬리표를 "Integer 갈래"에 붙여도 되는 것입니다. 그러나...


실험 정신이 투철한 사람이 아니고서는, 그리고 이러한 꼬리표에 붙어 있는 객체들이 Smalltalk에서 얼마나 중요한 객체인지를 알고 있는 사람은 함부로 이런 꼬리표를 건드리지 않습니다. (여러분에게 부탁합니다. 제발 이런 꼬리표를 건드리지 마십시오. Smalltalk에 어떠한 영향을 미칠지 저도 장담 못합니다. ^^:)


작은 꼬리표

여기서는 일단 작은 꼬리표에 대해서 먼저 알아보도록 하겠습니다. 실제로 꼬리표를 사용할 때에는 작은 꼬리표나 큰 꼬리표나 별반 차이가 없습니다. 그렇지만 Smalltalk 로 프로그램을 작성하는 경우에 큰 꼬리표보다 작은 꼬리표를 훨씬 더 많이 사용하게 될 것입니다.


작은 꼬리표이건 큰 꼬리표이건, Smalltalk 에서 꼬리표를 사용하려면 먼저 "이러이러한 이름의 꼬리표를 쓰겠노라" 고 Smalltalk에게 알려주는 과정이 필요한데, 이를 "꼬리표를 선언한다"라고 부릅니다. 즉 내가 꼬리표를 쓰고 싶다면 꼬리표를 쓰기 전에 Smalltalk에게 꼬리표를 쓰겠다고 알려주어야 한다는 것입니다. 꼬리표를 선언하는 방법은 작은 꼬리표와 큰 꼬리표가 서로 다릅니다. 우리는 지금 작은 꼬리표에 대해서 알아보고 있으므로, 여기서는 작은 꼬리표를 어떻게 선언하는지 알아봅시다.


작은 꼬리표는 글토막의 맨 처음에 다음과 같이 선언합니다.

| a b c |


보다시피 새로 막대(vertical bar) 사이에 꼬리표로 쓸 이름들을 빈칸으로 구분해서 늘어놓기만 하면 됩니다. 위의 경우는 a, b, c라는 세 개의 꼬리표를 쓰겠다고 Smalltalk에게 알려주고 있군요.


물론 두 글자 이상의 꼬리표도 사용할 수 있습니다.

| apple anInteger temperatureToday anInternetAddressBook |


위와 같이 한 개의 단어로 이루어진 꼬리표는 물론 두 개 이상의 단어로 이루어진 꼬리표도 사용할 수 있습니다. 다만 이 때 새로운 단어의 첫 글자에는 반드시 대문자를 사용하도록 합시다. 물론 작은 꼬리표의 첫 글자는 소문자이기 때문에 첫 번째 오는 단어에는 이 규칙이 적용되지 않습니다. 만약 anInternetAddressBook 을 aninternetaddressbook 이라고 쓰게 되면 어떻게 알아볼 수 있겠습니까? 그러므로 조금 번거롭더라도 단어와 단어를 쉽게 구분하고 바탕글을 쉽게 읽을 수 있게 하기 위해서 이름을 신중하게 짓는 것이 매우 중요합니다. 아, 그리고 Smalltalk에서 꼬리표나 이름의 길이에는 제한이 없습니다만, 지나치게 이름이 길어지면 글쇠판을 많이 눌러야 하기 때문에 귀찮아지고, 읽기에도 좋지 않으므로 비교적 짧으면서도 뜻이 분명한 이름을 짓도록 해야 할 것입니다.


이제 작은 꼬리표를 선언하는 방법을 알았으니, 선언된 꼬리표를 객체에 붙이는 방법을 알아봅시다. 작은 꼬리표나 큰 꼬리표 모두 똑같은 방법을 사용합니다.

꼬리표이름 := 계산식.


위와 같이 하면 ":=" 오른쪽에 있는 계산식을 모두 가늠하여 그 결과로 남겨진 객체에 왼쪽의 꼬리표를 붙이게 되는 것입니다. 위에서 ":=" 기호를 "붙임표"(binder)라고 부릅니다. ":=" 오른쪽에 있는 객체에게 왼쪽에 있는 꼬리표를 붙이기 때문에 이런 이름이 붙게 되었습니다.


이렇게 해서 객체에 꼬리표를 달게 되면 이제부터 꼬리표가 바로 객체의 새로운 이름이 되는 것입니다. 만약

| a |
a := 3.


이라는 글토막이 있다면, 이제부터 a 라고 부르면 "3" 을 가리키게 되는 것입니다.


그럼 이제부터 꼬리표를 사용하는 방법에 대해서 실제로 바탕글들을 가늠해 보면서 알아보도록 합시다. 바탕글을 가늠하기 전에 알림판의 모든 내용을 지워주십시오.


바탕글 1::꼬리표와 붙임표의 사용법

| a |
a := 3.
a printOn: Transcript.


[실행 결과]
3 (알림판에)


알림판에 "3"이 나타났습니다. 왜냐하면 3 에 a 를 붙였고, 따라서 이제부터 "3" 은 "a" 라는 새로운 이름으로 부를 수 있게 된 것입니다. 즉

a printOn: Transcript.


3 printOn: Transcript.


와 완전히 같은 글토막이 되는 것입니다. 이제 a라고 하면 언제나 3 을 가리키게 됩니다. 왜냐하면 3에 a가 __붙어있기__ 때문입니다.


그럼 다음 바탕글을 가늠해 보십시오.


바탕글 2::계산식 사용하기

| a |
a := 3 + 4.
a printOn: Transcript.
Transcript cr.


[실행 결과]
37 (알림판에)


알림판을 보면 "37"이라는 글자가 쓰여 있을 것입니다. 여기서 앞의 "3"은 바탕글 1 의 결과이고, 뒤의 "7"이 바탕글 2 의 결과입니다. 눈여겨보십시오. 바탕글 2 에서

a := 3 + 4.


라고 했습니다. 붙임표(:=)에 대해서 설명할 때, 붙임표 오른쪽의 계산식을 가늠하여 얻은 결과에 := 왼쪽의 꼬리표를 붙인다고 했습니다. 위에서 붙임표 오른쪽의 계산식을 모두 가늠하면 당연히 7이 될 것이고, 결과적으로 위의 글토막은

a := 7.


이 됩니다. 그럼 이제부터 a는 7에 붙어 있게 됩니다. 결국

a printOn: Transcript.


문장은

7 printOn: Transcript.


와 똑같은 문장이 되는 것입니다. 이것이 바로 꼬리표의 특성입니다.


바탕글 2 의 마지막에 Transcript에게 #cr 지시를 보낸 것은 결과를 좀 더 확인하기 쉽게 하기 위함입니다. 즉 하나의 결과를 표시한 다음 줄을 바꾸어 주면 다음 바탕글을 가늠할 때 편리하지 않겠습니까?


그럼 이번에는 꼬리표를 이용해서 의미 있는 일을 해 봅시다.


이 글을 쓰고 있는 지금은 매우 더운 여름입니다. 하루에도 기온이 30 도가 넘는 경우가 매우 많습니다. (여기는 대구거든요.) 그런데 어제의 기온은 28 도였고, 오늘의 기온은 32도였습니다. 그러면 기온의 평균은 얼마나 될까요? 이제 그것을 Smalltalk에서 꼬리표를 사용해서 알아보도록 합시다.


바탕글 3::꼬리표를 이용해서 기온 평균 구하기.

| temperatureToday temperatureYesterday temperatureAverage |
temperatureToday := 32. "섭씨"
temperatureYesterday := 28. "섭씨"
temperatureAverage := 
        ((temperatureToday + temperatureYesterday) / 2).

Transcript nextPutAll: '어제의 기온은 '.
temperatureYesterday printOn: Transcript.
Transcript nextPutAll: '도이며, 오늘의 기온은 '.
temperatureToday printOn: Transcript.
Transcript nextPutAll: '도 입니다.'; cr.

Transcript nextPutAll: '오늘과 어제의 기온 평균은 '.
temperatureAverage printOn: Transcript.
Transcript nextPutAll: '도 입니다.'; cr.


[실행 결과]
어제의 기온은 28도이며, 오늘의 기온은 32도 입니다.
오늘과 어제의 기온 평균은 30도 입니다.


오랜만에 긴 바탕글을 만나게 되었습니다. 그렇지만 바탕글이 길어도 내용이 어렵지는 않습니다. 많은 양에 겁먹지 말고, 차근차근 바탕글을 위에서부터 분석해 나가다 보면 나중에는 전체 바탕글을 이해할 수 있습니다.


그럼 먼저 바탕글 3 의 첫 줄을 보십시오.

| temperatureToday temperatureYesterday temperatureAverage |


꼬리표 세 개를 쓰겠다고 Smalltalk에게 알려주는 것입니다. 꼬리표의 이름이 아무리 길어봤자 결국에는 꼬리표입니다. 우리의 바탕글에서는 오늘의 기온(temperatureToday), 어제의 기온(temperatureYesterday), 그리고 기온의 평균(temperatureAverage), 이렇게 세 개의 꼬리표를 달았습니다.


다음에는 이렇게 만든 꼬리표를 각각 적당한 객체에 붙이면 됩니다.

temperatureToday := 32. "섭씨"
temperatureYesterday := 28. "섭씨"


"32"와 "28"은 평범하게 이를 데 없는 정수 객체입니다. 그러나 여기에 의미가 부여되기 시작합니다. 바로 "32"에는 "오늘의 기온"이, 그리고 "28"에는 "어제의 기온"이라는 의미가 부여되게 됩니다. 32와 28을 Smalltalk 세상에 존재하는 수많은 객체들 중의 일부이지만, 이제 이 객체에 꼬리표가 붙었으므로 두 개의 객체들은 충분한 의미가 있는 것입니다. 그런데...


위에서 큰따옴표(" ")안에 들어 있는 글자는 무엇일까요? 기억 나십니까? 바로 풀이글입니다. "2.3.3."에서 글자와 글줄을 다룰 때(#16) 우리는 잠시 풀이글에 대해서 알아본 적이 있습니다. 다시 말하자면, Smalltalk 는 큰따옴표 안에 들어 있는 모든 내용을 무시해 버립니다. 따라서 큰따옴표 안에 어떤 문자를 넣어도 상관이 없다는 것입니다. 그래서 보통은 바탕글에 설명을 달아줄 때 풀이글을 사용합니다.


바탕글 3 에서는 우리가 꼬리표를 붙인 객체들이 다름아닌 "섭씨 온도"(degree centigrade)임을 나타냅니다. 온도에는 섭씨와 화씨(fahrenheit) 두 가지의 종류가 있다는 것은 아시지요? 그래서 여기서는 그런 혼동을 피하기 위해서 풀이글로써 명시 해 둔 것입니다. 아무튼 위의 두 문장이 실행되면 비로소 "32"와 "28"은 의미를 갖게 됩니다.

temperatureAverage := 
        (temperatureToday + temperatureYesterday) / 2.


위의 문장은 온도의 평균을 구하는 부분입니다. 즉 어제의 온도와 오늘의 온도를 더해서 그것을 다시 2로 나누게 되면 평균값이 구해집니다. 이렇게 해서 얻어진 결과에 다시 temperatureAverage 라는 꼬리표를 붙이게 됩니다. 만약 위의 계산식이 단순히 지시를 내리기 위한 인자(parameter)에 실려서 전달되고 말았다면 아무런 의미를 가지지 못헀을 것입니다. 그러나 저렇게 계산된 객체에 temperatureAverage 꼬리표를 붙여 둠으로써, 저 객체는 사라지지 않고 필요할 떄 언제든지 부를 수 있게 된 것입니다. 이렇게 꼬리표는 원하는 객체에게 바로 붙여서 의미를 부여할 때에도 쓰이지만, 계산의 결과값으로 생기는 객체에게 붙여두어서 그 객체(=계산의 결과)를 나중에 다시 사용할 때에도 필요합니다.


이제 어제의 온도, 오늘의 온도, 온도의 평균을 모두 구했으니, 이것을 알림판에 뿌려주기만 하면 됩니다. 이는 #nextPutAll:과 #printOn:지시를 이용하면 어렵지 않게 할 수 있습니다. 간간히 줄을 바꾸어주기 위해서 #cr 지시도 필요하겠지요?

Transcript nextPutAll: '어제의 기온은 '.
temperatureYesterday printOn: Transcript.
Transcript nextPutAll: '도이며, 오늘의 기온은 '.
temperatureToday printOn: Transcript.
Transcript nextPutAll: '도 입니다.'; cr.


위에서 #nextPutAll:이 실행되는 것은 별반 다를 것이 없습니다. 오히려 꼬리표가 가리키는 객체에 #printOn: 지시가 내려지는 것이 중요합니다.


앞서 우리는 temperatureToday와 temperatureYesterday에 각각 32 와 28 을 붙였고, 그것을 여기에서 그대로 알림판에 써 낸 것입니다. 마지막 줄에 있는 #cr 지시는 줄을 바꾸기 위해 주어진 것이고, Transcript에 보내지게 될 것이므로, 잇따름 지시(cascaded message)를 사용했습니다.

Transcript nextPutAll: '오늘과 어제의 기온 평균은 '.
temperatureAverage printOn: Transcript.
Transcript nextPutAll: '도 입니다.'; cr.


역시 위의 문장도 별로 어려울 것이 없습니다. #nextPutAll: 지시를 이용해서 뼈대가 될 글줄을 출력하고, 계산에 의해서 생성된 객체에 temperatur Average 꼬리표를 붙여 두었으니 그것을 그대로 사용하기만 하면 됩니다. 역시 줄을 바꾸기 위해서 있다름 지시로 #cr을 내렸습니다.


바탕글 3 이 아직도 복잡해 보입니까? 만약 여기까지 읽었는데 무슨 말인지 제대로 이해가 가지 않으면 처음부터 다시 이 글을 읽어봅시다. 여기서 만약 제대로 개념을 잡지 못하게 되면 앞으로 많은 혼동을 일으키게 될 것입니다. 그러니 서두르지 말고 천천히 꼬리표와 붙임표에 대해서 이해하도록 합시다.


앞에서 꼬리표는 여기 저기에 마음대로 떼었다 붙였다 할 수 있다고 헀습니다. 아래의 바탕글을 살펴봅시다.


바탕글 4::꼬피표가 가리키는 값은 바뀔 수 있음

| temperature |
temperature := 28.
Transcript nextPutAll: '어제의 기온은 '.
temperature printOn: Transcript.
Transcript nextPutAll: '도 입니다.'; cr.

temperature := 32.
Transcript nextPutAll: '오늘의 기온은 '.
temperature printOn: Transcript.
Transcript nextPutAll: '도 입니다.'; cr.


위에서

temperature printOn: Transcript.


라는 똑같은 명령이 수행되었지만, temperature 꼬리표에 어떤 객체가 붙어 있는지에 따라서 다른 값이 알림판에 나타났습니다. 이렇게 한 번 어떤 객체에 꼬리표를 붙여놓아도 다른 객체에 꼬리표를 붙이면 예전에 붙어 있던 객체는 떨어져 나가고 새로운 객체가 붙게 됩니다. 즉 굳이 객체를 떼어내지 않아도 옛날 객체는 Smalltalk가 알아서 떼내어 줍니다. 중요한 것은 이렇게 꼬리표에는 필요에 따라서 여러 가지 객체들을 붙여둘 수 있다는 것입니다.


그런데 만약에 꼬리표를 쓴다고 선언해 놓고 아무런 객체도 붙이지 않았다면 어떻게 될까요? 다음 바탕글을 펴 봅시다.


바탕글 5::아무 것도 붙지 않은 꼬리표

| temperature |
Transcript nextPutAll: '오늘의 기온은 '.
temperature printOn: Transcript.
Transcript nextPutAll: '도 입니다.'; cr.


[실행 결과]
오늘의 기온은 nil도입니다.


실행 결과에서도 볼 수 있듯이 어떤 꼬리표가 처음에 만들어질 때, 즉 어떤 객체에도 붙이지 않은 꼬리표는 기본적으로 헛(nil) 객체에 붙어 있습니다.

nil class        UndefinedObject


nil은 위에서 보시다시피 UndefinedObject 갈래의 실체인데, "아무 것도 없는, 허무한, 허공의, 비어 있는, 의미 없는"이라는 뜻을 가지고 있습니다. 원래 UndefinedObject라는 갈래 이름 자체가 "의미 없는"이라는 뜻이기 때문에 nil의 속성을 잘 나타내고 있습니다.


위에서 보다시피 꼬리표를 만들었으면 반드시 그 꼬리표를 어떤 객체에 붙인 다음에 사용해야 합니다. 그렇지 않으면 꼬리표는 nil에 붙에 되고, nil이 알아들을 수 없는 지시를 사용하게 되면 Smalltalk가 발자취 창을 표시하면서 잘못된 동작을 수행하게 됩니다. 다음 바탕글을 가늠해 봅시다.


바탕글 6::아무 것도 붙지 않은 객체 그대로 사용하기

| temperature |
(temperature + 3) printOn: Transcript.
Transcript cr;


[실행 결과]
"UndefindedObject does not understand #+" 잘못 발생


위와 같은 경우 temperature는 아직 아무런 객체에도 붙여지지 않았으므로 nil 에 붙어 있을 것입니다. 결국은

(temperature + 3) printOn: Transcript.


(nil + 3) printOn: Transcript.


와 같은 꼴이 될 것이고, UndefinedObject의 실체인 nil은 덧셈을 할 줄 모르기 때문에 #+ 지시를 이해할 수 없다고 하는 어의 없는 결과를 낳게 됩니다. 그러므로 꼬리표를 사용할 때에는 먼저 제대로 객체를 붙였는지를 확인할 필요가 있습니다.


작은 꼬리표가 기구한 운명을 가지고 있다는 사실을 여러분은 모를 것입니다. 작은 꼬리표는 글토막이 가늠될 때, 오직 그 때만 존재합니다. 세로 막대 속에서 작은 꼬리표가 선언되고 바탕글에서 그 꼬리표가 할 일을 모두 수행하고 나면, 즉 바탕글의 실행이 끝나면 작은 꼬리표는 흔적도 없이 사라져버리게 됩니다. 다시 말하면 작은 꼬리표는 바탕글이 가늠될 때에만 존재하는 것입니다. 이런 까닭으로 작은 꼬리표를 일컬어 "하루살이 꼬리표" 또는 "잠깐 꼬리표"(temporary variables)라고도 부릅니다.


예를 들어봅시다.


바탕글 7

| color |
color := #red.
color.


[실행 결과]
☞ #red


바탕글 7 에 덩이를 씌워서 펴(display) 보면, 결과로 #red를 남깁니다. 그러니까 color라는 꼬리표를 이름값 #red에 붙인 다음 바로 그 꼬리표의 이름을 적어 주면 글토막의 결과값은 당연히 이름값 #red일 것입니다. 그런데 이 상태에서


바탕글 8

| color |
color


[실행 결과]
nil


바탕글 8 을 실행하면 그 결과로 nil이 나타납니다.


보다 시피 Smalltalk 는 바탕글 7 에서 color 꼬리표를 분명히 이름값 #red에 붙였지만, 바탕글 8 의 color에는 nil이 붙어 있습니다. 그러니까 바탕글 7바탕글 8 에는 똑같이 color라는 이름을 가진 꼬리표가 있었지만, 그 둘은 전혀 다른 꼬리표라는 것입니다. 왜냐하면 바탕글 8 이 실행되기 전에 바탕글 7' 의 실행이 끝났을 것이고, 바탕글 7 의 color 역시 바탕글의 실행이 끝남과 동시에 소멸해 버렸기 때문입니다. 이제는 왜 작은 꼬리표를 "하루살이 꼬리표"라던가 "잠깐 꼬리표"라고 일컫게 되었는지 이해가 되시나요?


작은 꼬리표의 이러한 특성으로 인해, 작은 꼬리표는 주로 바탕글에서 여러 가지 계산을 하여 그 결과로 얻은 객체를 붙잡아 매어두고 나중에 필요할 때 쓰기 위한 목적으로 사용됩니다. 어차피 바탕글 안에서만 의미를 가지는 꼬리표이기 때문에 바탕글 밖의 상황에 신경을 쓸 필요가 없는 것입니다. 앞으로 여러분이 많은 프로그램을 작성하게 되면 약방의 감초처럼 등장하는 작은 꼬리표의 활약을 지켜볼 수 있을 것입니다.


붙임표에 대하여

큰 꼬리표에 대해서 알아보기 전에 잠시 붙임표에 대한 몇 가지 특징을 짚고 넘어가기로 합시다. 붙임표(:=)는 생각보다 꽤 독특한 특성을 가지고 있으며, 이런 특성들은 우리가 바탕글을 쓸 때 알고 있어야 할 것들이라고 생각합니다.


붙임표는 크게 두 가지 성질을 가지고 있습니다.

  1. 오른쪽에 있는 식을 모두 가늠하여 그 결과에 왼쪽 꼬리표를 붙인다.
  2. 꼬리표를 붙인 객체 자체를 결과값으로 넘겨준다.


"1"의 경우에는 어렵지 않게 이해할 수 있을 것입니다. 그러나 "2"의 경우는 선뜻 이해가 되지 않을 것입니다. "2"번과 같은 특성을 이용한 바탕글을 보는 것이 더 쉬울 지도 모르겠군요. 앞서 바탕글 3 과 같은 일을 하지만 붙임표의 두 번째 특성을 이용한 바탕글을 아래에 제시하겠습니다.


바탕글 9::기온 평균 구하기 (붙임표 특성 사용)

| temperatureToday temperatureYesterday |
Transcript nextPutAll: '오늘의 기온은 '.
(temperatureToday := 32) printOn: Transcript.
Transcript nextPutAll: '도이며, 어제의 기온은 '.
(temperatureYesterday := 28) printOn: Transcript.
Transcript nextPutAll: '도 입니다.'; cr.

Transcript nextPutAll: '오늘과 어제의 기온 평균은 '.
(temperatureToday + temperatureYesterday) / 2 printOn: Transcript.
Transcript nextPutAll: '도 입니다.'; cr.


위의 바탕글이 바탕글 3 과 어떻게 다른지를 비교해 보십시오. 일단

| temperatureToday temperatureYesterday |


꼬리표가 세 개에서 두 개로 줄었습니다. 그리고 다른 곳은 크게 바뀌지 않았지만 아래와 같은 모양의 문장이 있습니다.

(temperatureToday := 32) printOn: Transcript.


위와 같이 바탕글을 쓸 수 있는 것은 바로 붙임표의 두 번째 특성 때문입니다. 오랜만에 글토막이 가늠되는 과정을 따라가 볼까~~요?

        (temperatureToday := 32) printOn: Transcript.
        ~~~~~~~~~~~~~~~~~~~~~~~
→      32                       printOn: Transcript.
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


결국 temperatureToday는 32에 붙여지고, 그 결과로 32를 되돌리게 됩니다. 마침내 우리의 자랑스런(?) #printOn: 지시는 바로 방금 붙여진 32 객체에게 전달되어 알림판에 "32"를 뿌리게 되는 것입니다. 물론 이는 temperature Yesterday에서도 마찬가지입니다.


이렇게 붙임표를 사용하여 어떤 객체에게 꼬리표를 붙이면 그 결과값으로 꼬리표가 붙은 객체를 다시 되돌려 준다는 것은 바탕글을 매우 간결하게 짤 수 있도록 도와줍니다. 꼬리표에 객체를 붙인 다음 계산을 수행하는 것이 아니라 꼬리표를 붙임과 동시에 그 값을 이용하여 계산을 하게 되면 두 줄 짜리 문장을 한 줄로 줄일 수 있지 않겠습니까? 물론 이런 방법을 사용해서 작성한 바탕글의 경우 간결하기는 하지만 바탕글을 읽기가 어려워진다는 문제점 또한 가지고 있습니다.


붙임표에 객체를 붙이면 붙인 객체를 그대로 돌려준다는 이 특성을 사용하여 다음과 같은 글토막을 생각할 수 있습니다. 아래 글토막을 펴 봅시다.

| sonAge fatherAge |
fatherAge := 25 + sonAge := 10.

 "UndefinedObject does not understand #addToInteger:" 발생.


위의 글토막을 가늠해 보면 잘못이 생깁니다. UndefinedObject 의 실체는 #addToInteger: 라는 지시를 이해할 수 없다는 말인데, 왜 저런 잘못이 일어났을까요? 역시 바탕글을 추적해 가면서 문제를 알아보도록 합시다.

fatherAge := 25 + sonAge := 10.
             ~~~~~~~~~~~


여기서는 붙임표가 있으므로, 일단 붙임표의 오른쪽에 있는 계산식을 먼저 가늠합니다. 따라서 붙임표 왼쪽에 있는 fatherAge 꼬리표에 대해서는 아직 신경을 쓰지 않아도 됩니다.


위에서 보는 바와 같이 먼저 "25 + sonAge" 가 가늠됩니다. 그런데... 지금 sonAge 는 어떤 객체에 붙어있습니까? 위의 글토막을 보게 되면, sonAge가 10 에 붙는 것은 이 다음의 일입니다. 즉 아직 sonAge는 그 어떤 객체에도 붙지 않았습니다. 결과적으로 sonAge는 nil을 가리키고 있을 것입니다. 네, 그것이 답입니다. nil은 바로 UndefinedObject 갈래의 실체입니다. 즉 nil 은 #addToInteger:라는 지시를 이해할 수 없다는 말입니다. 우리가 직접 nil 에 #addToInteger:라는 지시를 내려주지는 않았지만, #+ 지시가 수행되면서 발생한 것입니다. 아무튼 nil은 덧셈을 할 수 없기 때문에 문제가 생긴 것이고, 결국 이 문제는 sonAge에 아무런 값이 들어가 있지 않은 상태에서 글토막을 실행하려고 헀기 때문입니다.


그럼 위의 문제를 어떻게 고칠 수 있을까요? 네, 바로 괄호를 사용하면 됩니다. 글토막이 가늠되는 순서만 바꾸어주면 아무런 문제도 생기지 않는 것입니다. 이렇게 말입니다.

| sonAge fatherAge |
fatherAge := 25 + (sonAge := 10). 
 35


이렇게 하면 먼저 sonAge 꼬리표가 10 에 붙게 되고, 이 결과로 나오게 된 10과 + 왼쪽의 25가 더해져서, 결국 fatherAge는 35에 붙게 되는 것입니다.


붙임표를 사용한 다음에는 언제든지 꼬리표가 붙은 객체의 값이 그대로 전달된다는 이런 특성은 다음과 같은 바탕글을 만들어낼 수 있게 합니다.

| a b |
a := (b := 10).
 10


위의 경우 b를 10에 붙이게 되면 결과값으로 당연히 10이 남게 되고 결과적으로 a도 10에 붙게 되는 것입니다. 즉 b와 a는 똑같이 10에 붙어있게 되는 것입니다. 그런데 위의 글토막을 아래처럼 쓸 수도 있습니다.

| a b |
a := b := 10.
 10


Smalltalk에서 글토막은 언제너 왼쪽에서 오른쪽으로 가늠된다고 앞에서 이야기한 적이 있습니다. 그러나 붙임표에 대해서만은 예외가 적용됩니다. 왜 그럴까요? 그것은 바로 붙임표의 특성이기 때문입니다.


붙임표는 언제나 := 연산자의 __오른쪽__에 있는 계산식을 먼저 가늠해야 합니다. 즉

a := b := 10.


의 경우 첫 번째 붙임표를 가늠하기 위해서는 먼저 "b := 10"을 가늠해야 합니다. 따라서 결과적으로


"붙임표가 여러 개 있을 경우는 오른쪽에서부터 먼저 처리한다."


라는 규칙이 생기게 된 것입니다. 명심하십시오. 붙임표는 언제나 오른쪽에서 왼쪽으로 처리됩니다. 따라서 다음과 같은 글토막도 당연히 쓸 수 있습니다.

| a b c d e f g |
a := b := c := d := e := f := g := 0.


이는 a, b, c, d, e, f, g의 일곱 개의 꼬리표를 전부 객체 "0"에 붙이는 역할을 합니다. 이처럼 붙임표가 나란히 있을 때에는 가장 오른쪽에 있는 것부터 처리합니다.


그럼 이제 마지막으로 붙임표에 대해서 조금 어려운 부분을 이야기할까 합니다. 바로 붙임표의 본질에 대한 것입니다.


우리는 지금까지 여러 가지의 글토막을 가늠하면서 "+"는 덧셈을 하기 위한 연산자이면서 동시에 지시에 사용되는 길표(selector)라는 것을 알고 있습니다. 만일 우리가 다음과 같은 글토막을 지었다면

| smallInteger largeInteger |
smallInteger := 1.
largeInteger := 10000000000.
smallInteger + largeInteger.

 10000000001


"+ 10000000000"이라는 지시는 결국 "1"에 보내지게 됩니다. "1"은 "+"를 보고는 "덧셈을 해야겠다"고 생각하고, 뒤에 인자로 따라오는 "10000000000"을 가지고 덧셈을 하게 되는 것입니다. 그래서 결과로 "10000000001" 을 남기는 것이구요. 그런데 말일...

| smallInteger largeInteger |
smallInteger := 1.
largeInteger := 10000000000.
smallInteger := largeInteger.


위와 같은 글토막이 있다고 생각해 봅시다. 여기서 달라진 것은 맨 마지막 줄에 "+"가 있어야 할 자리에 ":="이 있다는 것입니다. 그러면 위의 글토막에서 ":= largeInteger"라는 지시가 "smallInteger"에 보내지게 되는 것일까요? 즉 ":= 10000000000"이 "1"에게 보내지게 될까요? 단도직입적으로 물어서, ":=" 은 지시를 나타내는 길표일까요?


":="이 길표인지 아닌지에 대해서 알아내려면 약간의 생각이 필요합니다. 만약에 ":="이 길표라면, 즉 객체 "1"에 ":= 10000000000"이 지시로써 내려지게 된다면, smallInteger 꼬리표에 영향을 주지 말아야 합니다. 그러나 분명히 ":=" 왼쪽에 있는 smallInteger 꼬리표는 바뀌고 맙니다. 즉 ":= 10000000000"이라는 지시는 결코 "1"에게 보내질 수 없습니다. 왜냐하면 붙임표에서 중요하게 여기는 것은 smallInteger가 붙어있는 객체 "1"이 아니라 "smallInteger" 자체이기 때문입니다. 따라서 ":=" 은 분명히 "+" 와는 다른 동작을 합니다. 그 어떤 지시라도 지시를 받은 객체 자체를 __깡그리__ 다른 객체로 바꿔칠 수는 없습니다. 이는 하극상이지요!


또 하나, ":=" 이 만약 "+" 와 같이 길표라면, 어떤 객체라도 ":=" 이라는 지시를 받을 수 있어야 합니다. 그런데. ":=" 왼쪽에는 반드시 꼬리표만 와야 합니다. 따라서 ":="은 길표가 아닙니다.


":=은 길표가 아니라 특별한 동작을 하는 연산자이다."


만약 ":="이 지시라면 ":=" 왼쪽에는 어떠한 객체가 와도 상관이 없어야 합니다. 그러나 ":=" 왼쪽에는 오직 꼬리표만 와야 합니다.


{{{1}}}


큰 꼬리표

큰 꼬리표(global variable)는 꼬리표의 이름 첫 글자가 대문자인 꼬리표를 말합니다. 이 꼬리표는 작은 꼬리표와는 좀 다른 특징을 가지고 있습니다. 작은 꼬리표가 바탕글이 실행될 때에만 존재하고 실행이 끝나는 즉시 소멸되어버리는 것과는 달리 큰 꼬리표는 한 번 만들어 놓으면 사용자가 그 꼬리표를 없애기 전까지 남아 있습니다. 심지어는 Smalltalk를 끝내고 난 다음 다른 일을 하나다, 한 일주일 쯤 뒤에 다시 Smalltalk 환경에 들어와도 여전히 살아 있습니다. 대단하지요?


작은 꼬리표를 쓸 때 우리는 언제나 꼬리표를 선언해 주어야 했습니다. 큰 꼬리표도 마찬가지입니다. 큰 꼬리표를 쓰려면 그 전에 "내가 이러이러한 큰 꼬리표를 사용할 것이다"라고 Smalltalk에게 알려주어야 합니다. 그럼 어떻게 하면 큰 꼬리표를 선언할 수 있을까요? 다음을 보십시오..

Smalltalk at: #MyName put: '김찬홍'.     '김찬홍'


여기서 우리는 Smalltalk라고 하는 새로운 객체를 사용하게 됩니다. 이 객체에게 #at:put 지시를 내려서 큰 꼬리표를 선언하게 됩니다. 작은 꼬리표의 경우에는 선언만 할 수 있지만, 큰 꼬리표의 경우에는 선언과 함께 그 꼬리표에 어떤 객체를 붙일지도 정해야 합니다. 위의 글토막에서 "MyName"이라는 새로운 큰 꼬리표를 선언했고, 여기에 '김찬홍'이라는 글줄을 넣었습니다. 이제 일터에서

MyName   '김찬홍'


위의 글토막을 펴보면 '김찬홍'이라는 결과를 얻을 수 있습니다. 즉 큰 꼬리표 MyName에 붙어 있는 '김찬홍'이라는 객체를 그대로 편 셈이 되는 것입니다. #at:put:에서 "at:" 열쇠말에는 새로 만들 큰 꼬리표의 이름을 넣어주고, "put:" 열쇠말에는 만들어지게 될 꼬리표를 붙일 객체를 표시해 줍니다. 조심할 것은, "at:"에 들어가는 큰 꼬리표의 이름 앞에는 반드시 우물 정(#)표를 붙어야 한다는 것입니다. 이것은 앞서 말했듯이 Smalltalk에서 꼬리표에 이름을 붙일 때에는 이름값을 사용한다는 것과 일치합니다. 만약

Smalltalk at: MyName put: '김찬홍'.


이라고 하게 되면 위의 문장은 "MyName이라는 꼬리표가 가리키고 있는 객체를 이름으로 하는 새로운 꼬리표"를 만든다는 것이 됩니다. 그러므로 새로운 큰 꼬리표를 만들 때에는 반드시 이름값을 넣어야 함을 잊지 말도록 합시다.


MyName이 꼬리표이기 때문에 언제든지 다른 객체에 바꿔 붙일 수 있습니다.

MyName := '홍길동'.      '홍길동'


위와 같이 하면 역시 MyName 꼬리표는 '홍길동'이라는 글줄에 붙게 됩니다. 이것은 작은 꼬리표나 큰 꼬리표나 다를 바가 없는 내용입니다.


앞에서 큰 꼬리표는 Smalltalk를 끝내더라도 없어지지 않는다고 했습니다. 그러면 실험을 해 봅시다. Smalltalk 를 지금 당장 끝내십시오. File > Exit Dolphin 메뉴를 사용하여 Smalltalk를 끝냅시다.


그 전에 잠깐!! Smalltalk를 끝낼 때 언제나 "현재의 상태를 본에 저장하겠느냐"고 물어볼 때 "예"(Yes)로 답해야 합니다. 그렇지 않으면 여러분이 만들어 놓은 큰 꼬리표를 저장하지 못하게 됩니다.


이제 다시 Dolphin Smalltalk를 실행시켜 봅시다. 그리고 일터에서

MyName   '홍길동'


위의 글토막을 펴 보면 Smalltalk를 끝내기 전에 붙여 두었던 '홍길동' 이라는 객체가 그대로 표시됨을 알 수 있습니다. 즉 MyName은 두 눈 시퍼렇게 뜨고 살아 있는 것입니다.


이렇게 Smalltalk 에 들어 있는 큰 꼬리표는 다름 아닌 Smalltalk의 본(image)에 그대로 저장됩니다. 우리가 Transcript, SmallInteger, Smalltalk 등의 큰 꼬리표를 언제나 사용할 수 있는 것도, 바로 이렇게 큰 꼬리표와 거기에 붙어 있는 객체들이 Smalltalk의 본(image)에 그대로 저장되기 때문입니다.


그렇기 때문에 Smalltalk 에서는 큰 꼬리표보다는 작은 꼬리표를 많이 사용합니다. 대부분의 사람들은 Smalltalk를 쓰다 보면 큰 꼬리표를 써야 할 때는 1년에 몇 번 안 된다고 하는 경우도 있습니다. 그만큼 Smalltalk에서는 큰 꼬리표를 만들어 쓰지 않습니다. 왜냐하면 큰 꼬리표보다 더 편리한 방법들이 많기 때문입니다.


보통 계산의 결과값을 잠시 보관해 두는데는 앞서 말했듯이 작은 꼬리표가 훨씬 편리합니다. 그리고 굳이 큰 꼬리표를 사용하고 싶으면 보통의 큰 꼬리표가 아닌 "갈래 꼬리표"(class variable)를 사용합니다. 이것은 갈래에 포장되어 있기 때문에 보통의 큰 꼬리표보다 안전합니다. 아무튼 큰 꼬리표는 되도록 새로 만들어서 사용하지 않는 것이 바람직합니다.


큰 꼬리표는 이렇게 한 번 선언해 주면 사용자가 지우기 전까지는 절대로 없어지지 않습니다. 그러면 이런 큰 꼬리표를 어떻게 지울까요? 어렵지 않습니다. Smalltalk 객체에게 #removeKey:라는 지시를 내려주면 됩니다.

Smalltalk removeKey: #MyName.    '김찬홍'


여기서 사용한 #removeKey: 지시는 MyName이라는 큰 꼬리표를 없애고 난 다음 그 꼬리표에 붙어 있던 객체를 결고값으로 남깁니다. 즉 꼬리표를 떼어 낸 다음 그 꼬리표가 붙어 있던 객체를 그대로 돌려줍니다. 당연한 말이지만, 이렇게 큰 꼬리표를 지우고 난 다음에는 그 꼬리ㅍ는 사용할 수 없게 됩니다.

MyName           "Error: Undeclared MyName" 발생


위와 같이 없는 꼬리표를 사용하게 된다면 당연히 "선언되지 않은 꼬리표"라고 Smalltalk가 투덜거릴 것입니다.


그런데 아무리 봐도 큰 꼬리표를 선언하는 것이 너무나 귀찮다고 생각하지 않습니까? 일일이 Smalltalk 객체에게 #at:put: 지시를 내린다는 것도 귀찮고요. 그래서 Smalltalk에서는 큰 꼬리표를 선언할 수 있는 또 다른 방법을 준비해 놓고 있습니다.


자, 이미 앞에서 MyName 이라는 큰 꼬리표를 지웠습니다. (지우지 않았다면 위에 있는 글토막을 가늠해서 지워주십시오.) 그 상태에서 아래의 글토막을 입력해 봅시다.

MyName := '김찬홍'.


그러면 다음 그림 3-6 처럼 MyName 을 큰 꼬리표로 만들 것인지를 붇는 대화 상자가 표시됩니다.


그림 3-6::큰 꼬리료를 만들지 물어보는 모습 (Stp3-6.gif)


즉 MyName이라는 꼬리표에 어떤 객체를 붙이려고 하는데, Smalltalk 에는 현재 해당하는 꼬리표가 없고, 따라서 사용자에게 이 꼬리표를 만들 것인지를 물어보고 있습니다.


여기서 "예"(Yes)라고 답하면 MyName이라는 꼬리표를 만든 다음 여기에 '김찬홍'을 붙이게 되고, "아니오"(Mo)나 "취소"(Cancel)를 선택하면 MyName은 존재하지 않는 꼬리표가 되어 "Error: Undeclared MyName"이라는 잘못을 일으키게 됩니다. 즉 사용자의 선택에 따라서 위의 글토막이 실행될 수도 있고, 잘못을 일으킬 수도 있는 것입니다.


그렇다면 왜 큰 꼬리표를 만들 때 일일이 사용자의 확인 과정을 거치게 했을까요? 그것은 앞서도 말했듯이, 큰 꼬리표는 한 번 만들어지면 고의로 지우기 전까지는 그 내용이 보존되어 있고, 또한 프로그래머가 원래는 작은 꼬리표를 만들도록 했는데 실수로 꼬리표 이름 첫 글자를 대문자로 적을 수도 있기 때문입니다.


아무튼 보통은 Smalltalk 객체에게 #at:put: 지시를 이용해서 꼬리표를 선언하는 것이 정석이고, 그것을 간단하게 하기 위해서 붙임표를 바로 사용할 수 있는 선택적 선언 방법이 있다는 것을 알아두면 좋겠습니다.


그런데 우리가 큰 꼬리표를 만들고 지울 때 사용헀던 Smalltalk 객체를 잠시 생각해 봅시다. 도대체 이 객체가 무엇이기에 우리가 큰 꼬리표를 만들 때 이 객체에게 지시를 내렸을까요? 다음과 같이 해서 Smalltalk 객체가 어떤 것인지 알아봅시다.

Smalltalk  a SystemDictionary(#MourningWeakArray -> MourningWeakArray #STBFiler -> STBFiler ... 하략)


Smalltalk 객체는 SystemDictionary 갈래의 실체입니다. SystemDictionary 는 Smalltalk의 모듬(collection)의 한 종류로써, 사전(Dictionary)를 윗갈래로 하고 있습니다. 사전에는 보통 올림말(표제어, keyword)과 그 올림말을 설명해 주는 내용(value)이 들어 있는데, SystemDictionary도 이런 역할을 합니다. 아무튼 나중에 모듬을 공부하게 될 때 이 Dictionary에 대해서도 함께 알아보게 될 터인데, 아무튼 지금으로써는 Smalltalk를 펴 보는 것만으로 도저히 어떤 값이 들어있는지 알기가 어렵습니다. 아무래도 객체 탐색기를 이용해야 할 것 같습니다.


그림 3-7::Smalltalk 객체를 탐색하는 모습 (Stp3-7.gif)


탐색기의 왼쪽 널을 가만히 살펴봅시다. 뭔가 눈에 익은 것들이 보이지 않습니까? Array부터 시작해서 ArrayedCollection, Boolean, Dictionary,... 어라? False, Float, Integer, Magitude, Number까지? MyName도 보이는군요. 방금 우리가 만들어 두었던 큰 꼬리표인데... SmalltInteger도 있군요. 그리고 Smalltalk까지? 어허~ 더구나 Transcript도 있습니다.


이제 Smalltalk 객체의 실체를 알았습니까? 바로 이 객체는 Smalltalk 에 존재하는 모든 큰 꼬리표를 담아두고 관리하는 곳입니다. 물론 Smalltalk 객체는 이것 말고도 다른 중요한 일들을 하지만, 아무튼 객체 탐색기를 통하여 살펴본 결과로 우리가 사용하는 모든 꼬리표가 여기에 다 들어가 있게 되는 것입니다. 따라서 우리들이 Smalltalk 객체에게 #at:put을 이용해서 새로운 항목을 만들어 주면 역시 그 항목이 큰 꼬리표가 되는 것이고, #removeKey: 를 통하여 어떤 항목을 지우게 되면 큰 꼬리표 역시 따라서 없어져 버리는 것입니다. 신기하지 않습니까?


자, 그럼 이제 한 가지 재미있는 실험을 해 봅시다. 일단 이 실험을 하기 전에 File > Save Image 를 사용해서 현재의 상태를 본에 받아 두십시오. 그렇지 않으면 크게 후회할지도....


준비가 되었으면 다음 글토막을 가늠해 봅시다.

Processor := nil.


Processor 역시 큰 꼬리표인데, Smalltalk에서 명령 수행을 처리하는 중요한 객체가 이 꼬리표에 붙어 있습니다. Processor 는 컴퓨터의 CPU와도 같은 중요한 꼬리표인데... 위와 같이 이 꼬리표를 허망하게 nil 에 붙여버리면 Smalltalk는 그 즉시 다운 되어버리고 맙니다. 정말 허망하게 말입니다...


이처럼 Smalltalk에서 이미 사용하고 있는 큰 꼬리표에 다른 객체를 붙이는 것은 매우 위험합니다. 그러므로 큰 꼬리표를 사용할 때에는 세심한 주의가 필요합니다.


변수라고 해야 옳다! 꼬리표? 말도 안돼!


프로그래밍 언어를 한 번이라도 접해본 분들이 이 글을 읽으면서 아마 몇몇 분은 저를 많이 비난하셨을 지도 모른다고 생각하니 등골이 오싹합니다. 그것은 바로 "꼬리표"라는 말 때문입니다.


Smalltalk에서는 variables이라고 부릅니다. 엄밀히 말하면 이는 "변수"가 맞습니다. 그러나 소위 다른 프로그래밍 언어에서 쓰는 "변수"와 Smalltalk의 "variable"과는 너무너무 다른 개념이 흐르고 있습니다. 보통 다른 언어에서는 "변수"를 "방"이라고 생각합니다. 그래서 "변수 a에 3을 넣는다"라는 말을 곧잘 합니다. 그러나 Smalltalk 에서의 variable은 방이 아닙니다. Smalltalk 의 variable 은 철저히 다른 객체를 가리키고 있습니다. 이런 개념이 달라지는 이유는 바로 "값 의미"와 "가리킴 의미"의 차이 때문입니다.


"값 의미"(value semantic)란 어떤 값이 변수에 대입될 때 그 "값" 자체가 이동하는 것으로 봅니다. 즉

a := 3;


이라는 문장이 수행되면 3이라고 하는 값이 a라는 방 안에 들어가 있다는 생각입니다. 이것은 결국 값 자체가 이동한다는 것을 말해주고 있습니다.


"가리킴 의미"(reference semantic)는 반면에 어떤 값이 변수에 대입될 때 해당하는 값 자체가 움직이는 것이 아니라 다만 변수가 값을 가리키는 바늘과 같은 의미를 지니고 있습니다. 따라서

a := 3.


이라고 명령했을 때 이는 "a라는 변수는 3을 가리킨다"고 보면 됩니다. 바꾸어 말하면 "a라는 변수가 3 이라는 값에 붙어 있다"고 표현할 수 있습니다. 즉 "3"이라고 하는 값이 이동하는 것이 아니라는 것입니다.


Smalltalk 에서의 variable 은 이러한 "가리킴 의미"를 가지고 있습니다. 그래서 variable을 "변수"가 아니라 "꼬리표"라고 부른 것입니다. 우리는 지나칠 정도로 값 의미에 익숙해져 있습니다. 그래서 저는 처음부터 값 의미가 아닌 가리킴 의미를 제대로 전달하기 위해서 "꼬리표"라는 말을 사용했습니다. 즉 꼬리표를 어떤 객체에게 붙여서 그 꼬리표가 객체를 가리키게 하는 의미를 나타내려고 한 것입니다.


값 의미와 가리킴 의미를 아직까지 제대로 파악하지 못하신 분이 있는 것 같은데, 다음의 글토막을 살펴봅시다.

| a b |
a := #(1 2 3).
b := a.
b at: 1 put: 1000.
a.


위의 글토막을 펴 보면 결과값으로 무엇이 나오겠습니까? 바로 #(1000 2 3) 이라는 결과가 나옵니다. 분명 글토막의 끝에는 a가 기록되어 있습니다. 그러므로 꼬리표 a는 #(1000 2 3)에 붙어있다는 말입니다.

a := #(1 2 3).


의 경우에는 물론 여지 없이 a라는 꼬리표를 #(1 2 3)이라는 배열에 붙인 것입니다. 그 다음

b := a.


라고 헀습니다. 문제는 여기서부터 비롯됩니다. 만일 위의 문장을 값 의미로 해석한다면 b와 a는 서로 다른 객체여야 합니다. 즉 a의 값이 그대로 b에 전해지는 것입니다. 다시 말하면 a의 값이 그대로 b로 복사되는 것입니다. 그러나 Smalltalk의 꼬리표는 철저히 가리킴 의미를 지킵니다. 즉 위의 경우 b 는 a 가 가리키는 객체를 가리킵니다. 여기서 절대로 b를 위해서 새로운 객체가 만들어지지 않습니다. 이것은 바로 다음에서 여실히 드러납니다.

b at: 1 put: 1000.


b는 현재 #(1 2 3)이 들어있는 배열이고, 여기에 #at:put: 지시를 내려서 배열 성분의 값을 바꿀 수 있습니다. 그런데 만약 값 의미였다면, a와 b는 전혀 다른 객체이므로 b를 아무리 바꾸어도 a에는 아무런 영향이 없어야 합니다. 그러나

a.


위의 경우 #(1000 2 3) 이라는 결과를 얻고 말았습니다. 즉 b 를 바꾸었는데 왜 a의 값이 바뀌느냐 이겁니다. 바로 이것이 가리킴 의미의 특성을 전적으로 나타내어 주는 예제입니다.

b := a.


는 "a 꼬리표가 붙어 있는 객체에 b라는 꼬리표를 붙인다"라고 해석하면 이해하기가 쉽습니다. 즉 하나의 객체에 a라는 꼬리표도 붙이고, b라는 꼬리표도 붙입니다. 결국 부르는 이름만 다르지 a 라고 부르건 b 라고 부르건 결국 똑같이 #(1 2 3)을 부르게 되는 것이고 b가 붙어있는 객체에 어떤 지시를 내린다는 것은 결국 a에 붙어 있는 객체에게 지시를 내린다는 것과 다를 바 없는 것입니다. 그래서 a의 값이 바뀌게 되는 것입니다.


그러면 다른 언어들에서는 값 의미와 가리킴 의미를 어떻게 사용하고 있는지 알아봅시다.


베이식

베이식의 경우에는 변수에 어떤 값을 대입할 때에는 언제나 값 의미가 적용됩니다. Visual Basic의 경우에는 함수나 절차의 형식 인자에 실인자를 대입할 때에는 특별한 지시가 없는 이상 가리킴 의미가 적용지만, ByVal 을 이용해서 값 의미라고 명시해줄 수 있습니다. 그렇지만 Basic에서는 원칙적으로 포인터가 없기 때문에 가리킴 의미를 구현할 수 없습니다.


C

C에서는 모든 것이 값 의미로만 전달됩니다. 변수를 대입할 때도 그렇고, 함수의 실인자가 형식인자에 대입될 때에도 마찬가지로 값 의미가 부여됩니다. 그러나 포인터를 사용해서 가리킴 의미를 구현할 수는 있습니다. C 언어에서 포인터가 왜 그렇게 중요한 위치를 차지하게 되었는가 하면, 바로 가리킴 의미를 구현하기 위해서인 것입니다. 실제로 프로그래밍에서는 값 의미보다는 가리킴 의미가 더 효율적이고 더 자주 사용됩니다. C의 배열이 함수에 전달될 때에는 언제나 포인터로써 전달됩니다. 그러므로 C 에서의 배열 변수는 가리킴 의미가 어느 정도 적용된다 하겠습니다. 그러나 이는 값 의미밖에 부여되지 않은 언어에서 가리킴 의미를 구현하기 위한 하나의 술수에 불과합니다.


C++

C에 가리킴 의미를 적용하지 않았기 때문에 발생하는 문제점을 해결하기 위해서 C++에서는 '참조자'(reference)를 만들었습니다. 이 참조자를 이용하면 비로소 가리킴 의미를 부여할 수 있습니다. 함수의 실인자를 형식인자에 전달할 때는 물론 하나의 변수에 참조자를 사용하여 다른 이름을 부여할 수 있습니다. 즉 꼬리표를 만들 수 있는 것입니다. 그러나 C++의 참조자는 매우 제한적이고, 프로그램의 실행 중에 자유롭게 참조자의 값을 바꿀 수는 없습니다. 즉 한 번 꼬리표가 붙으면 다른 대상물에 꼬리표를 바꾸어 붙일 수 없습니다. 또한 이런 참조자가 있다고는 하지만 class에서 생성한 객체의 경우에는 여전히 포인터에 의해서만 가리킴 의미를 구현할 수밖에 없고, 설상가상으로 참조자가 쓰이고 쓰이지 않음에 따라 똑같이 보이는 문장이라도 어떨 때에는 값 의미가, 또 다른 경우에는 가리킴 의미가 부여되어 매우 혼란스럽습니다. 여기에 한 술 더 떠서 "=" 이라고 하는 대입 연산자의 행동을 사용자가 마음대로 바꿀 수 있기 때문에, 값 의미와 가리킴 의미가 언제 어느 때 적용되는지에 대한 규칙이 없게 됩니다. 그야말로 기름에 불을 지른 격이나 마찬가지입니다. 따라서 왠만하면 C++를 쓰는 사람들도 참조자 쓰기를 기피합니다. 차라리 조금 위험 부담이 있기는 하지만 포인터를 사용해서 일관되게 가리킴 의미를 표현해 내는 것이 더욱 일관성 있고 덜 헷갈리기 때문입니다. → C++언어의 문법에 예외가 많고 복잡한 것이 많은 이유가 여기에 있습니다.


Object Pascal

정통 파스칼의 경우 변수를 대입할 때에는 값 의미가 부여되고, 형식 인자에 실인자가 전달될 때에도 값 의미가 부여됩니다. 다만 형식 인자를 선언할 때 "var" 이라는 지정어를 사용해서 값 의미를 적용시킬 수 있습니다. 그러나 이는 매우 제한적입니다. 객체 파스칼의 경우는 이러한 전통적인 파스칼의 속성을 그대로 물려받습니다. 여기에 객체를 다룰 때에는 값 의미가 아닌 가리킴 의미가 적용됩니다. 즉 같은 대입문이라도

a := 3;


의 경우에는 값 의미가 적용되고

a := TButton.Create(Self);
b := a;


등과 같이 객체를 다룰 때에는 가리킴 의미가 사용됩니다. 따라서 사용자는 객체를 다루고 있는지 그렇지 않은지를 제대로 파악해서 값 의미가 적용되는지 아니면 가리킴 의미가 적용되는지를 명확하게 꿰뚫고 있어야 합니다. 그리고 Delphi 2.0 부터 등장한 '긴 문자열'(long string)과 Delphi 4.0부터 등장한 동적 배열(dynamic array) 역시 가리킴 의미를 가지고 있습니다. 논리적이고 명확한 파스칼 문법을 흩어놓는 주된 원인이 객체 파스칼에서의 "객체" 라니, 아이러니하지 않습니까?


Java

Java의 경우도 객체 파스칼과 별반 다르지 않습니다. Java 에서는 자료형이 크게 두 부류로 나누어지는데 하나는 "원시형"(primitive type)이라 하고, 다른 하나는 "객체형"(object type)이라 합니다. 원시형의 경우 철저히 값 의미가 적용되고 객체형의 경우에는 가리킴 의미가 적용됩니다. 함수에 실인자를전달할 때에도 원시형의 경우는 언제나 값 의미가 적용되고, 객체형의 경우에는 언제나 예외없이 가리킴 의미가 부여됩니다. 객체 파스칼에서처럼 원시형의 경우에 가리킴 의미로 인자를 전달할 수 없습니다.


Smalltalk

이제 우리가 공부하고 있는 Smalltalk를 살펴봅시다. Smalltalk에서는 모든 대입문이 가리킴 의미를 가지고 있습니다. "3", "4" 등과 같은 경우도 물론 똑같은 의미가 부여됩니다. 길수(method: 다른 언어에서의 함수나 절차와 같음)의 인자를 전달할 때에도 어김없이 가리킴 의미를 사용합니다. 그 어디에도 값 의미를 사용하는 법이 없습니다. 따라서 Smalltalk 는 C 언어와는 반대로 값 의미를 구현하기 위한 지시를 가지고 있습니다. 이를테면 위에서 예로 들었던 글토막을 조금만 고쳐서

| a b |
a := #(1 2 3).
b := a copy.            "객체의 복사본 생성"
b at: 1 put: 1000.
a.


이와 같이 바꾸게 되면 a와 b는 서로 영향을 받지 않는 완전히 다른 값을 가지게 됩니다. 즉 명시적으로 b가 붙어 있는 것은 a가 붙어있는 객체가 아니라 b의 복사본입니다. 어떤 객체에게 #copy 지시를 내리면 해당 객체와 똑같은 값을 가진 객체를 하나 더 만들어 내는, 즉 지시를 받은 객체의 복사본을 만들어 냅니다. 따라서 b를 아무리 건드려도 a에는 영향을 미칠 수 없게 되는 것입니다. 즉 명시적으로 값 의미를 구현한 것입니다.


결론적으로 Smalltalk에는 일관된 철학이 있습니다. 상황에 따라서 이렇게 바뀌고 저렇게 바뀌는 것이 아니라 언제나 일관된 철학과 통일된 개념이 흐르고 있으며, 이 개념 위에서 문법이 규정됩니다. Smalltalk에서는 언제나 가리킴 의미를 사용합니다. 이는 정말 탁월한 선택이라고 할 수 있습니다. 값 의미는 변수에 따라서 값이 이리 갔다가 저리 갔다가 하지만, 가리킴 의미에서는 객체는 가만히 있고 여기에 꼬리표가 붙듯이 변수가 붙어서 명령을 수행합니다. 정말 이 하나의 상황만을 보아도 모든 것을 객체 중심적으로(Object Oriented) 생각하는 객체 지향 철학이 베어나지 않습니까?


자, 이제 왜 필자가 "variable"을 "변수"라고 번역하지 않고 "꼬리표" 라고 번역했는지 이해하실 수 있습니까? 앞으로 이 글에서는 계속 "꼬리표"라는 말을 사용하겠습니다. 이것이 더 자연스럽습니다.


값 의미와 가리킴 의미, 조금 혼동되는 개념이기는 하지만 Smalltalk 에서 "꼬리표"의 존재를 제대로 이해하기 위해서 꼭 필요한 요소라고 생각하십시오. 그리고 기존 언어에서 사용하고 있던 개념을 Smalltalk 에 그대로 적용하려고 하지 마십시오. 오히려 혼동만 초래할 뿐입니다. Smalltalk를 공부할 때에는 늘 새로운 개념에서 바라보는 습관을 기르십시오. 그래야 제대로 알 수 있습니다. 아무것도 모르는 초등학생이 두 달 만에 Smalltalk 환경에서 헬리콥터 시뮬레이터를 만들었다는 사실을 상기하십시오. 기존에 가지고 있던 고정 관념은 우리들을 옭아매는 오랏줄이 됩니다.


지금까지 우리는 Smalltalk에서 매우 중요한 개념인 "꼬리표"에 대해서 알아보았습니다. 꼬리표는 무수히 존재하는 객체들 중에 필요한 곳에 붙여서 객체가 의미를 갖게 하고, 계산 결과로 남는 객체들에 붙여 두고 나중에 참조할 수 있도록 하며, 또한 바탕글을 쉽게 읽을 수 있도록 해 줍니다. 아무튼 이러한 꼬리표는 작은 꼬리표와 큰 꼬리표로 나뉘어지게 되고, 각각은 그 쓰임에 따라서 적절하게 활용해야 합니다.


여기까지 이해했으면 이제 여러분은 Smalltalk의 문법 중에 90%를 익힌 것과 다름이 없습니다. 이제 앞으로 오늘 공부한 꼬리표가 정말 큰 역할을 하게 될 것입니다.


지금까지는 글자들만 가지고 놀았으니 다음 가름에서부터는 그래픽 적인 요소를 다루어보도록 합시다. 이제 꼬리표를 알았으니, 여차 하면 객체에 꼬리표를 붙여서 여러분의 것으로 만드시기 바랍니다. 그리고 한 가지 더! 언제 어디서 어떤 일이 발생할 지 모르는 법이므로, Smalltalk를 사용할 때에는 늘 본(image)을 잘 떠 두는 것을 잊지 않도록 합시다.


운동장 놀이

바로 앞 가름에서 우리는 객체에게 꼬리표(variable)를 붙이고 그것을 객체의 이름으로 부르는 법을 알아보았습니다. 이제 이 꼬리표를 사용하게 되면 객체를 만들어서 그것을 헛으로 날려버리지 않아도 된다는 말입니다.


지금까지 우리는 Smalltalk를 "공부"해 왔습니다. 시험을 따로 본다거나 하는 부담이 없기는 하지만, 그래도 공부를 한다는 것은 힘든 일입니다. 실은 필자도 한 편의 글을 완성하기 위해서는 상당히 많은 생각이 필요합니다. 그래서 이제 필자도 문자나 숫자만 나오는 그런 것보다는 좀 더 재미있는 그림을 가지고 놀고 싶어졌습니다. 놀아봅시다. "놀이는 바로 학습이다"라는 말이 결코 헛된 말이 아니니까요.


우리가 놀 때 주로 운동장에서 놉니다. 공을 가지고 놀건, 몸으로 부대끼는 놀이를 하건 아무튼 운동장이라는 장소에서 마음껏 놉니다. 우리도 비록 공을 차고 몸을 움직이는 놀이는 아니지만 운동장을 만들어서 놀 것입니다. 열심히 놀고 있는데 다른 곳에서 방해를 받으면 썩 기쁜 일은 아닐 테니까요.


그런데 Smalltalk에서는 모든 것이...? -이제 여기까지 하면 "또 그 소리야?"라고 핀잔을 주시겠지만, 아무튼- 네, 그렇습니다. "객체"(object)입니다. 따라서 우리가 놀아야 할 운동장 역시 Smalltalk 에서는 하나의 객체입니다. 우리는 Smalltalk에서 운동장을 만들고 여기에 동그라미, 세모, 네모와 같은 그림들을 올려놓고 이것들을 가지고 놀 것입니다. 이러한 그림들은 아이들이나 가지고 노는 것이 아니냐는 분들이 혹시 계실지도 모르겠는데, 여러분은 적어도 Smalltalk에서는 아직도 아이입니다. 불만 있습니까? :)


아무튼 이번 가름에서는 운동장을 만들고 여기에 여러 가지 도형들을 올려놓고 놀아볼 것입니다. 그 전에 먼저 일터 꼬리표와 꾸러미에 대해서 알아봅시다. 아직 우리는 운동장이랑 도형을 어디에서 어떻게 꺼내와야 할지도 모르기 때문에, 일단 일터 꼬리표와 꾸러미에 대해서 알고 나면 그 다음부터는 정말 재미있게 놀 수 있을 것입니다.


일터 꼬리표

꼬리표(variables)는 크게 "큰 꼬리표"(global variable)와 "작은 꼬리표"(local variable)의 두 가지가 있다고 말씀드린 바 있습니다. 그런데 큰 꼬리표와 작은 꼬리표는 또다시 그 특성에 따라 여러 가지로 나누어집니다. 그 중에서 오늘은 "일터 꼬리표"(workspace variable)에 대해서 알아볼까 합니다.


우선 일터 꼬리표를 어떻게 선언하고 사용하는지 알아봅시다. 일터 꼬리표도 꼬리표의 일종이므로 당연히 꼬리표를 쓰기 전에는 선언을 해야 합니다. 일터 꼬리표를 선언하는 방법은 아주 간단합니다. 우선 File > New 명령을 실행해서 새 일터를 하나 열어봅시다. 이제 당분간은 이렇게 열린 일터에서 작업하도록 합시다.


일터에서 아래 글토막을 펴 보십시오. 일터 꼬리표는 작은 꼬리표의 일종이므로, 꼬리표 이름의 첫 글자는 반드시 영어 소문자이어야 함을 기억합시다.

a := 3.          3


우리는 지금 "a"라는 꼬리표를 전혀 선언하지 않았습니다. 그저 정수 "3"에 "a"라는 꼬리표를 붙여놓기만 했습니다. 만일 "a"가 작은 꼬리표였다면 분명히 잘못이 있어야 합니다. 그러나 이렇게 해서 생긴 "a"는 이제 일터 꼬리표가 된 것입니다. 꼬리표 "a"는 어떤 객체에 붙어있을까요?

a        3


a는 분명히 3에 붙어 있습니다. a가 평범한 작은 꼬리표였다면 글토막의 실행이 끝났을 때 그 값은 없어져야 옳았습니다. 그러나 지금 a는 당당하게 살아있고, 3이라는 객체에 붙어있습니다. 이는 우리가 a를 작은 꼬리표가 아닌 일터 꼬리표로 선언했기 때문입니다.


이렇게 일터 꼬리표를 선언하려면, 그냥 평범하게 일터 꼬리표에 객체를 붙여주기만 하면 됩니다. 큰 꼬리표를 선언할 때 Smalltalk객체에게 #at:put: 지시를 내릴 필요 없이

MyName := '김찬홍'.


이라고만 해도 자동으로 큰 꼬리표가 만들어졌던 것을 기억해 보십시오. 물론 큰 꼬리표를 만들 때에는 사용자의 확인 과정을 거치지만, 일터 꼬리표의 경우는 그 확인 과정을 생략하고 바로 꼬리표를 만듭니다.


아무튼 이렇게 해서 만들어진 일터 꼬리표는 사용자가 이 꼬리표를 없애기 전까지 그대로 남아 있습니다. 그렇지만 일터 꼬리표는 다른 일터에서는 전혀 그 존재를 알 수 없습니다. 즉 현재 일터 꼬리표가 선언된 그 일터에서만 의미가 있다는 말입니다. File > New 를 이용해서 새 일터를 연 다음 아래 글토막을 펴 보십시오.

a        "Undeclared a" 잘못 발생


이 일터에서는 "a"라는 꼬리표를 사용하지 않았기 때문에 잘못이 일어난 것입니다. 이처럼 일터 꼬리표는 그 꼬리표가 만들어진 일터 안에서만 의미가 있습니다. 결과를 확인했다면 바로 전에 열었던 일터는 닫읍시다.


그런데 이러한 일터 꼬리표를 쓸 수 없는 곳이 있습니다. 바로 알림판인데, 지금 알림판을 열고 아래의 글토막을 가늠해 보십시오.

a := 3.          "Undeclared a" 잘못 발생


알림판에서는 일터 꼬리표를 쓸 수 없습니다. 왜냐하면, 알림판은 그 어떤 경우에도 소멸되지 않는 객체이기 때문입니다. 일터나 객체 탐색기 등은 언제든지 그 창을 닫아서 없애버릴 수 있지만, 알림판은 절대로 없애버릴 수 없습니다. 왜냐하면 알림판을 닫아 버리면 알림판이 없어지는 것이 아니라 Smalltalk 환경이 종료되기 때문입니다. 따라서 한 번 만들어진 일터 꼬리표를 지울 방법이 없는 것입니다. 그래서 알림판에서는 일터 꼬리표를 사용할 수 없습니다.


그럼 이러한 일터 꼬리표는 왜 사용하는 것일까요? 그것은 바로 객체의 값을 좀 더 오래 보존해야 할 경우가 있기 때문입니다. 작은 꼬리표의 경우는 글토막이 가늠될 때에만 의미가 있고 글토막의 실행이 끝나면 그 꼬리표가 없어지게 됩니다. 그렇다고 큰 꼬리표를 사용하는 것도 바람직하지 않습니다. 왜냐하면 큰 꼬리표는 Smalltalk 환경의 어느 곳에서나 사용해야 하는 중요한 객체에게만 붙여야 하기 때문입니다. 그래서 작은 꼬리표와 비슷한 성질을 가지고 있지만, 동시에 큰 꼬리표처럼 한 번 만들어지면 지우기 전 까지는 없어지지 않게 하기 위해서 일터 꼬리표가 사용됩니다.


그런데 일터 꼬리표와 작은 꼬리표의 이름이 같다면 어떻게 될까요? 지금 우리가 열어 놓은 일터에는 "a"라는 이름의 일터 꼬리표가 있으며, 3이라는 객체에 붙어 있습니다. 그런데 이 상태에서 아래의 글토막을 가늠해 보십시오.

| a |
a := 3 + 4.
 "Cannot redefine shared variable a" 잘못 발생


위에서는 a라는 이름의 작은 꼬리표를 쓰려고 했습니다. 그러나 이미 a라는 일터 꼬리표가 있기 때문에 작은 꼬리표를 만들 수 없다는 것입니다. 그러면 어떻게 해야할까요? 방법은 "a"라고 하는 일터 꼬리표를 없애는 것입니다. 그런데 공교롭게도 한 번 만들어진 일터 꼬리표를 없애기는 그리 쉽지가 않습니다. 다만 일터 꼬리표는 일터가 닫힐 때 모두 없어지기 때문에 이를 이용하는 수밖에는 없습니다. 다음의 순서를 따르십시오.

  1. "Edit > Select All"을 실행하여 일터의 모든 내용을 선택한다.
  2. "Edit > Cut"을 실행하여 선택한 내용을 잘라둔다.
  3. 일터를 닫는다.
  4. "File > New"를 실행하여 새 일터를 만든다.
  5. "Edit > Paste"를 실행하여 잘라둔 내용을 붙인다.


이렇게 하면 예전에 있던 일터는 이미 지워졌기 때문에 그 안에 딸려있던 일터 변수 또한 없어지게 되지만, 일터에 있었던 글은 잘라내기를 통하여 임시 기억장소(clipboard)에 저장되어 있기 때문에 기존에 일터에서 작업하던 글토막들은 고스란히 남게 됩니다. 만약 실수로 일터 꼬리표를 만들어서 지워야 할 때에는 위의 방법을 사용하면 됩니다. 조금 번거롭기는 하지만 지금으로써는 위의 방법이 가장 현명한 방법이라고 생각됩니다.


일터 꼬리표


원래의 Smalltalk에는 일터 꼬리표라는 것이 존재하지 않았습니다. 따라서 개발된 지 오래된 Smalltalk 시스템은 일터 꼬리표를 지원하지 않았습니다. 그러나 Dolphin을 비롯한 Smalltalk-MT 등의 경우는 일터 꼬리표를 사용할 수 있습니다. 예전처럼 간단한 계산 결과를 보관해 둔다거나 문자 위주의 자료 처리를 주로 하던 시스템에서는 굳이 일터 꼬리표가 필요 없겠지만, 그래픽, 멀티미디어 등을 다루어야 하고, 또한 여러 개의 창(window)을 사용자가 직접 만들어서 꼬리표를 붙여 사용해야 할 경우에는 이러한 일터 꼬리표는 매우 편리한 도구라고 생각됩니다.


꾸러미에 대해서...

집 안에 물건이 많아지면 통이나 수납장을 이용해서 정리해 둡니다. 그래야만 물건을 찾기도 쉽고 필요 없는 물건을 버리기도 쉬울 뿐만 아니라, 너저분해지지 않기 때문입니다.


Smalltalk에도 많은 수의 객체가 존재합니다. 그런데 이러한 객체들은 서로가 독립적인 존재들이기는 하지만, 어느 정도의 연관성은 가지고 있습니다. Smalltalk 시스템의 밑바닥을 이루는 시스템 객체들, 사용자와 대화하기 위해서 필요한 객체들, Smalltalk의 계산을 담당하는 객체들 등 여러 가지 객체들이 있습니다. 그 뿐인가요? Smalltalk로 프로그램을 만들다 보면 필요에 의해서 여러 가지의 객체들을 만들게 됩니다. 이런 객체들이 만약 아무런 대책 없이 이곳 저곳에 널브러져 있다면 심각한 문제가 아닐 수 없습니다.


다행이 Dolphin Smalltalk에서는 이럴 때를 대비하여 "꾸러미" 라는 것이 준비되어 있습니다. 꾸러미(package)는 쉽게 말해서 가방이라고 생각하면 됩니다. 관련 있는 물건들을 한 곳에 모아두고 필요할 때 찾아서 쓰거나 필요 없는 것은 버릴 수 있는 것처럼 꾸러미에 객체를 담아두었다가 필요하면 버릴 수 있고, 또한 꾸러미에서 객체들을 쉽게 찾아서 쓸 수 있는 것입니다.


Dolphin Smalltalk 가 설치되면 처음에는 네 개의 꾸러미가 등록되어 있습니다. 그 중 "Dolphin"이라는 꾸러미는 Dolphin을 구성하는 기본적인 객체들과 갈래들이 들어 있으며, "Ole COM"은 Windows에서 프로그램간에 자료를 교환하거나 다른 언어에서 만들어진 객체에게 지시를 내리기 위해서 필요한 객체와 길수들이 들어있습니다. "Scribble" 과 "Etch-A-Sketch" 는 Smalltalk로 만들어진 "그림판" 의 예제이며, 이 그림판을 구성하는 객체들이 들어 있습니다.


아무튼 이러한 꾸러미들을 관리하기 위해서는 "꾸러미 탐색기"(package browser)라는 도구가 필요합니다. Tools > Package Browser 메뉴를 실행하면 그림 3-8 과 같은 꾸러미 탐색기를 볼 수 있을 것입니다.


그림 3-8::꾸러미 탐색기 (Stp3-8.gif)


꾸러미 탐색기는 크게 세 부분으로 구성되어 있습니다.


왼쪽 널(pane)은 현재 Dolphin 에 설치되어있는 꾸러미를 나타내는 것이고, 오른쪽 널에는 해당 꾸러미에 들어있는 갈래(class), 길수(method), 큰 꼬리표(global), 자원(resource), 기타 명령(script) 등을 보여줄 수 있습니다. '자원'은 간단히 말하면 메뉴, 글자, 그림, 창을 이루는 화면 구성 등 프로그램을 실행하는데 필요한 자료를 말하며, 기타 명령은 주로 꾸러미를 설치할 때나 제거할 때 실행되어야 할 명령들을 써 놓는 곳입니다.


아래쪽 널에서는 꾸러미의 등록 정보, 갈래꼴(class definition), 길수꼴(method definition) 등을 볼 수 있으며, 선택된 꾸러미에 대해서 설명을 달아줄 수도 있습니다.


꾸러미 탐색기에 있는 메뉴를 통해서 새로운 꾸러미를 만들 수도 있고, 만들어진 꾸러미를 디스크에 보관할 수도 있으며, 보관된 꾸러미를 Dolphin에 설치하거나 설치된 꾸러미를 제거할 수도 있습니다. 필요 없는 꾸러미는 제거해 두는 것이 기억 공간을 넓게 사용할 수 있는 방법이겠지요?


우리가 놀아볼 운동장과 그 안에 필요한 여러 가지 객체들은 바로 이러한 꾸러미에 들어있으며, 꾸러미 탐색기를 통해서 꺼내야 하는 것들입니다.


Dolphin Smalltalk의 꾸러미


꾸러미(package)라는 개념이 처음 Smalltalk가 나올 때부터 등장했던 것은 아니었습니다. 그러나 시간이 갈수록 많은 수의 객체가 생겨나게 되고, 이러한 객체를 정리해 둘 필요가 생기게 되었습니다. 아울러 다른 사람이 만든 갈래나 객체를 배포할 때에도 나름대로의 방법이 필요했습니다. 예전에는 단순히 갈래꼴(class definition)과 길수꼴(method definition) 등을 .st 라는 파일에 담아서 배포하였지만, 이는 죽어있는 글자들일 뿐이지 살아있는 객체는 아닙니다.


Smalltalk 환경을 직접 만드는 여러 회사들은 자신들만의 독특한 방법으로 객체를 포장하는 기술을 개발해 왔습니다. Dolphin Smalltalk의 Package도 그 중에 하나입니다. 필자가 다른 Smalltalk를 많이 사용해보지 않아서 잘 모르겠지만, 비교적 최근에 만들어진 Smalltalk 환경들은 어떤 방법으로든지 Dolphin Smalltalk의 꾸러미처럼 객체를 보관하거나 정리할 수 있는 방법을 제공한다는 것을 알 수 있을 것입니다. 여러분이 Dolphin Smalltalk를 계속해서 사용하게 된다면 꾸러미는 여러분에게 매우 중요한 요소가 될 것입니다. 나중에 이러한 꾸러미에 객체나 갈래들을 담는 방법을 설명해야 할 때가 있을 것입니다. 그 때까지는 꾸러미 탐색기에서 열심히 꾸러미를 설치하고 제거하는 것을 익혀두는 것이 좋겠습니다.


운동장 위에서

지금까지 일터 꼬리표와 꾸러미에 대해서 어느 정도 알아보았으니, 이제는 정말로 운동장을 가지고 놀아봅시다. 우리의 장난감은 "Playground"라는 꾸러미에 들어있습니다. 일단 장난감을 꾸러미에서 꺼내어 Dolphin 에 설치하는 작업을 먼저 해 봅시다.

  1. "Tool > Package Browser"를 실행하여 꾸러미 탐색기를 연다.
  2. "Package > Install"을 실행하여 'Open Package' 상자를 연다.
  3. Dolphin Smalltalk가 설치된 폴더로 이동한다.
    ☞ 보기) C:\Program Files\Dolphin Smalltalk 98\
  4. 해당 폴더 아래에 위치하는 Samples 폴더를 연다.
  5. Playground 폴더를 선택해서 연다.
  6. 'Playground.pac'파일을 클릭해서 선택하고 "열기"를 누른다.


여기까지 하면 "Playground" 꾸러미가 Dolphin에 설치되고, 화면 아래쪽 널에는 꾸러미에 대한 간략한 설명(comment)이 표시될 것입니다.


그림 3-9::Playground 꾸러미를 설치한 화면 (Stp3-9.gif)


이 꾸러미에는 운동장과 그 속에 들어갈 도형을 만들기 위한 갈래들이 들어있습니다. 우리는 이제 이 갈래에게 지시를 내림으로써 운동장을 만들고, 운동장에 동그라미, 세모, 네모 등의 각종 도형을 올려놓을 수 있게 됩니다. 여기서 잠시 알림판(transcript)을 보면, "Loading in Playground package from D:\Progr..." 와 같은 글이 쓰여있음을 볼 수 있는데, 이는 Playground 꾸러미를 성공적으로 설치했다는 것을 알려주기 위한 것입니다.


그럼 이제 필요 없는 일터는 모두 닫고 File > New 메뉴를 실행해서 새로운 일터를 하나 만들어 봅시다. 앞으로 운동장에서 행해지는 여러 가지 작업들은 모두 여기서 실행하게 될 것입니다.


먼저 해야할 일은 운동장을 만드는 것이겠지요? 바로값의 경우에는 그 객체를 나타내는 이름만 적어주면 객체가 만들어졌습니다. "3"이라고 써 주면 정수 객체 3이 만들어지는 것입니다. 그렇지만 운동장은 바로값이 아닙니다. 그래서 결국은 우리가 사용할 객체는 우리 스스로가 만들어야 합니다.


일터에서 아래 글토막을 가늠해 보십시오.

Playground new.


"Playground"는 방금 우리가 Playground 꾸러미를 설치할 때 생겼던 갈래입니다. 이 갈래에 #new라는 지시를 내렸습니다. 그랬더니 어떻게 되었습니까? "a Playground" 라는 제목을 가진 창이 하나 생겼습니다. 지금 생긴 창은 엄연한 하나의 객체입니다. 즉 Playground 갈래의 실체(instance)인 것입니다. 지금 생긴 창을 이리 저리 움직여 보고 크기도 바꾸어 보십시오. 신기하게도 방금 우리가 Playground 갈래에 #new 지시를 내려서 만든 창은 기본적으로 창이 갖추어야 할 모든 것을 가지고 있습니다. 그런데...


지금 우리가 Playground 갈래에 #new라는 지시를 내려서 운동장을 만들었지만, 이것을 어떻게 해야할지 도무지 알 수가 없습니다. 마우스를 이용해서 창을 조절할 수는 있지만, 정작 Smalltalk로는 어떠한 명령도 줄 수가 없습니다. 바로, 객체를 만들어 놓고는 그 객체에 아무런 꼬리표도 붙여주지 않았기 때문에 이런 일이 생긴 것입니다. 창이 화면에 표시되어 있기는 하지만 이 창에 어떤 명령을 주려고 해도 우리는 꼬리표를 붙이지 않았기 때문에 아무런 지시도 내려줄 수 없는 것입니다.


일단 열려 있는 창을 닫고, 앞에서 공부한 대로 꼬리표를 붙여봅시다. 새로 만들어진 객체에게 우리는 'aPlayground' 라는 이름의 꼬리표를 붙일 것입니다. 아래의 글토막을 가늠해 보십시오.

aPlayground := Playground new.


방금 나타났던 것처럼 똑같이 "a Playground"라는 제목의 창이 표시될 것입니다. 그러나 이렇게 해서 만들어진 운동장에는 이제 'aPlayground'라는 일터 꼬리표가 붙어 있습니다. 이제 언제든지 이 꼬리표를 이용하면 열려 있는 창에 지시를 내릴 수 있습니다. aPlayground는 일터 꼬리표이기 때문에 이렇게 꼬리표를 붙여놓기만 하면 자동으로 꼬리표가 만들어지고, 사용자가 일터를 닫기 전까지는 없어지지 않습니다.


우리의 운동장에게 "화면의 왼쪽 귀퉁이로 가라"고 지시를 내려봅시다. 아래의 글토막을 가늠해 보십시오.

aPlayground position: 0@0.


위에서 사용한 #position:은 쇠마디 지시로써, 바로 뒤에 인자로 넘겨주는 자리로 창의 위치를 바꾸는 역할을 합니다. 창의 위치나 크기를 지정하는 방법에 대해서는 나중에 좀 더 자세히 알아보겠지만, 여기서 중요한 것은 "Playground" 갈래에게 #new 라는 지시를 내려서 객체 하나를 얻었으며, 여기에 'aPlayground' 라는 꼬리표를 붙였고, 이렇게 해서 붙여진 꼬리표를 사용해서 객체에게 명령을 내렸다는 것입니다. 이런 일련의 과정들을 이해하는 것이 중요합니다.


운동장을 만들었으니 이제는 가지고 놀 모양들을 만들어 봅시다. 우리는 동그라미, 세모, 네모를 만들 것입니다. 아래의 글토막을 가늠해 봅시다.

dongle := Circle new. 
semo := Triangle new. 
nemo := Square new.


앞서 우리가 운동장을 만들 때 "Playground" 갈래에 #new라는 지시를 보냈던 것을 기억하십니까? 마찬가지로 우리가 동그라미, 세모, 네모 모양을 만들기 위해서는 원하는 갈래에 #new라는 지시를 내려주어야 합니다. 우리는 동그라미를 만들고 'dongle' 꼬리표를 붙였고, 세모를 만들어서 'semo'를 붙였으며, 네모를 만들어서 'nemo'를 붙였습니다. 이렇게 세 개의 객체를 만들고 그 객체를 가장 적당하게 부를 수 있도록 꼬리표를 붙여주었습니다.


이렇게 어떤 객체를 만들고 싶을 때에는 그 객체가 속해 있는 갈래에게 #new 라는 지시를 내려주면 됩니다. 물론 지금까지 우리가 다루어온 대부분의 객체들은 바로값이었습니다. 그러나 운동장, 동그라미, 세모, 네모 등은 결코 바로값으로 나타낼 수 없는 객체들이고, 이런 종류의 객체들을 만들어 내기 위해서 우리는 그 객체가 속한 갈래에 "새로운 실체를 만들어 내라"는 뜻으로 #new지시를 내린 것입니다. 이렇게 생각하면 갈래는 실체를 만들어 내는 공장이라고 할 수도 있겠습니다.


운동장을 봅시다. 그런데 우리가 만들어 놓은 모양들은 아무데도 보이지 않습니다. 어찌된 일일까요? 그것은 우리가 모양만 만들어 놓고 운동장에 그것들을 넣지 않았기 때문입니다.


아래 글토막을 가늠해 보십시오.

aPlayground add: dongle.
aPlayground add: semo.
aPlayground add: nemo.


우리가 Playground 갈래에 #new 지시를 내려서 만든 운동장 객체는 #add: 라는 지시를 알아듣습니다. #add: 는 쇠마디 지시이고, 인자로 모양을 넘겨주면 그 모양을 운동장에 집어넣습니다. 우리는 방금 동그라미, 세모, 네모를 운동장에 넣었습니다. 운동장을 보면 그림 3-10 과 같이 글토막이 가늠될 때마다 각각의 모양이 생기는 것을 볼 수 있습니다. 신기하지 않습니까? :)


그림 3-10::운동장과 동그라미, 세모, 네모 (Stp3-10.gif)


이번에는 운동장에 있는 모양을 없애봅시다. 아래와 같이 해 보십시오.

aPlayground remove: dongle. 
aPlayground remove: semo. 
aPlayground remove: nemo.


어떻습니까? 위의 글토막이 하나씩 가늠될 때마다 운동장에 있었던 모양들은 자취를 감추게 됩니다. 즉 운동장 객체는 #remove: 라는 쇠마디 지시를 알아듣는다는 말입니다.


우리는 운동장에서 모양을 없앤 것뿐입니다. 즉 dongle, semo, nemo 라는 꼬리표가 붙어있는 객체들은 아직 없어지지 않았습니다. 그러므로 마음만 먹으면 언제든지 다시 운동장에 넣을 수 있습니다. 이번에는 앞에서 배운 "잇따름 지시"(cascaded message)를 이용해서 도형을 한꺼번에 집어넣겠습니다.


바탕글 1::잇따름 지시를 이용해서 운동장에 도형 넣기

aPlayground
        add: dongle;
        add: semo;
        add: nemo.


지금까지는 글토막을 하나씩 가늠해도 되었지만, 위의 바탕글은 잇따름 지시이기 때문에 덩이(block)를 씌워서 한꺼번에 가늠해야 합니다. 아무튼 위의 바탕글을 실행하면 운동장에서 다시 동그라미, 세모, 네모의 도형을 볼 수 있습니다.


이제 운동장도 만들었고 거기에다가 동그라미, 세모, 네모의 도형도 집어넣었으니, 지금부터 이들 도형을 가지고 놀아봅시다. 우선 도형을 움직이는 것부터 시작합시다.


화면은 수많은 점으로 이루어져 있습니다. 이런 점을 화소(畵素, pixel)라 부르는데, 화면상에서 거리나 크기를 젤 때에는 이런 화소를 사용합니다. 그래서 우리가 도형을 움직일 때에도 "오른쪽으로 몇 화소만큼 움직여라"는 식으로 지시를 내려주어야 합니다.


지금 운동장에 있는 네모에게 "30 화소만큼 오른쪽으로 움직여라"고 지시를 내려봅시다. 네모 객체에는 nemo 라는 꼬리표가 붙어 있기 때문에

nemo moveRight: 30.


위의 글토막을 가늠하면 그림 3-11 처럼 빨간색 네모가 오른쪽으로 약간 움직였다는 것을 볼 수 있을 것입니다. 잘 구분이 가지 않으면 그림 3-10 과 비교를 해 보시기 바랍니다. 분명히 네모는 오른쪽으로 30 화소를 이동했습니다. 우리가 네모 객체에게 #moveRight: 지시를 내려주었기 때문입니다.


이번에는 파란색 삼각형을 위로 옮겨보도록 하겠습니다. 도형을 위로 움직이려면 #moveUp: 지시를 사용합니다. 역시 움직일 거리를 화소 단위로 넣어 줍니다.

dongle moveUp: 100.


그림 3-12::#moveUp: 지시에 반응하는 삼각형 (Stp3-13.gif)


화면에서 100화소의 거기가 어느 정도 되는지 짐작이 가십니까? 처음에는 화소와 실제의 거리가 대칭이 잘 되지 않을 것입니다. 그렇지만 화소 좌표를 많이 이용하게 되면 어느 정도 거리를 계산할 수 있을 것입니다.


도형의 크기를 바꿀 수도 있습니다. 크기를 늘리려면 #growBy: 지시를 내려주면 되고, 반대로 크기를 줄이려면 #shrinkBy: 지시를 사용하면 됩니다. 역시 크기의 단위는 화소를 사용합니다.

semo growBy: 50. 
nemo shrinkBy: 30.


그림 3-13::도형의 크기를 조절하는 #growBy: 와 #shrinkBy: (Stp3-13.gif)


원래의 크기로 되돌리고 싶으면 늘였던 것은 줄이고, 줄였던 것은 늘리면 됩니다. 그러니까 semo 에게는 #shrinkBy: 를, nemo 에게는 #growBy: 를 내려주면 됩니다. 다음과 같이 말입니다.

semo shrinkBy: 50. 
nemo growBy: 30.


운동장에 있는 모든 도형은 일정한 각도만큼 회전을 시킬 수 있습니다. 이를 위해서는 #rotateBy: 지시를 사용합니다.

nemo rotateBy: 45.
semo rotateBy: 90.


지금 nemo의 경우에는 다이아몬드의 모양을 하고 있지만, 45 도 만큼 회전했더니 지면과 수평인 모양이 되었습니다. 마찬가지로 세모의 경우도 각도를 조절하여 회전시키면 화면 아래쪽을 바라보는 역삼각형의 모습이 된 것입니다. 그림 3-14 를 살펴보면 #rotateBy: 지시를 받은 도형이 어떻게 반응했는지를 살펴볼 수 있을 것입니다. 물론 동그라미의 경우는 이리 돌려보고 저리 돌려봐도 똑같은 모양이라는 것은 두말할 나위가 없겠지요?


그림 3-14::#rotateBy:에 반응하는 도형들 (Stp3-14.gif)


마지막으로 운동장에 있는 도형의 색깔을 바꾸어봅시다. 도형의 색깔을 바꾸기 위해서는 #color: 지시를 사용하면 됩니다. 이 #color: 지시는 "Color" 갈래의 실체를 필요로 합니다. 색깔 역시 바로값이 아니기 때문에 "Color" 갈래에 여러 가지 지시를 내려서 색을 나타내는 객체를 만들어야 합니다.


기본적으로 "Color" 객체는 #red, #green, #black, #blue, #yellow, #cyan, #magenta, #gray 등과 같이 우리가 흔히 사용하는 색깔 이름을 지시로 알아듣습니다. 그래서 빨간색을 나타내고 싶을 때에는 "Color red"와 같이 써 주면 되는 것입니다.


말로 하는 것보다는 직접 실행해 보는 것이 이해하기가 쉽겠지요? 지금 운동장에 있는 노란색 세모를 초록색으로 바꾸어 보겠습니다. 앞에서 말했듯이 어떤 도형의 색을 바꾸기 위해서는 해당하는 도형 객체에 #color: 라는 지시를 내려주어야 합니다.

semo color: Color green.


위의 경우 길수의 우선 순위에 따라서 먼저 "Color" 갈래에 외마디 지시(unary message)인 #green이 보내지게 됩니다. 그래서 결국 #green 지시를 받은 "Color" 갈래는 초록색을 나타내는 객체를 만들어내고, 이것이 쇠마디 길수인 #color:에 인자로 넘어가게 됩니다. 결국 semo가 가리키는 세모 객체의 색이 초록색으로 바뀌는 것입니다. 이런 원리로 네모와 동그라미의 색도 바꿀 수 있습니다.

nemo color: Color magenta.
dongle color: Color cyan.


이렇게 #color: 지시를 사용하면 도형의 색을 마음대로 바꿀 수 있습니다. 다음 그림 3-15 를 살펴보면 방금 실행한 세 개의 지시가 도형의 색깔을 어떻게 바꾸었는지를 알 수 있습니다.


그림 3-15::#color: 지시를 알아듣는 도형 (Stp3-15.gif)


그런데 "Color" 갈래에 내리는 길수로 만들어지는 색에는 한계가 있습니다. 예를 들면 "불그스레한 흰색"이나 "푸르스름한 회색"과 같이 말로 표현하기도 조금 애매한 색을 내려면 어떻게 해야 할까요? 이럴 때에는 색을 가리키는 이름보다는 아예 색상 표를 펼쳐놓고 내가 원하는 색을 골라서 쓰는 것이 편할 것입니다.


동그라미, 세모, 네모와 같은 모양들은 모두 #chooseColor 라는 외마디 지시를 알아듣습니다. 이 지시가 실행되면 색상 표가 나오고, 여기서 원하는 색을 선택하여 도형의 색을 결정할 수 있습니다.

dongle chooseColor.


위의 명령이 실행되면 그림 3-16 과 같이 색을 선택할 수 있는 화면이 나옵니다. 선택할 수 있는 색이 적은 경우에는 "사용자 정의 색 만들기"라는 단추를 누르면 보다 더 미세하게 색을 선택할 수 있습니다. 그림 은 "사용자 정의 색 만들기" 단추를 눌러서 선택한 화면입니다.


그림 3-16::#chooseColor로 열리는 색상 선택표 (Stp3-16.gif)


여기서 원하는 색을 선택한 후 "확인" 단추를 누르면 선택한 색이 도형의 색으로 바뀌게 됩니다. 이 밖에도 "Color" 갈래에게 내리는 지시에 따라서 색상(hue)과 체도(saturation)를 이용해서 색을 만들 수도 있고, 빛의 삼원색인 빨강, 초록, 파랑을 이용해서 색을 만들 수도 있습니다. 아무튼 나중에 색에 대해서 자세히 다룰 일이 있을 때 "Color"에 대해서 좀 더 깊이 알아보도록 하겠습니다.


이제 운동장을 가지고 놀았으니 깨끗이 정리를 해 봅시다. 운동장에 있는 도형을 제거하려면 "Playground" 갈래의 실체에게 #remove: 지시를 내려도 되지만 도형의 개수가 많아지면 일일이 #remove: 지시를 내리는 것이 귀찮을 때가 있습니다. 이 때 운동장에게 "청소하라"고 지시를 내려주면 모든 도형을 한꺼번에 지울 수 있습니다.

aPlayground clear.


위의 글토막을 실행하면 운동장에 있던 모든 도형이 지워집니다. 물론 각각의 도형에 꼬리표를 붙여두었다면 #add: 지시를 이용해서 언제든지 다시 운동장에 넣을 수 있습니다. 운동장을 다 썼으면 그냥 닫아버리면 그만입니다.


지금까지 살펴본 것처럼 어떤 객체에게 일을 지시하기 위해서는 객체가 알아들을 수 있는 지시를 내려주면 됩니다. 또한 간단한 지시가 아니라 부수적으로 어떤 일을 해야하는지를 자세하게 알려줄 필요가 있을 때에는 쇠마디 지시와 인자를 사용하면 됩니다. 이것이 객체지향 패러다임의 골자입니다.


더욱이 재미있는 것은 이러한 것들을 필요하면 언제든지 즉시 실행할 수 있다는 것입니다. 이것이 Smalltalk 의 큰 특징입니다. Smalltalk에서는 모든 것이 실시간(real-time)으로 처리되며, 따라서 객체 역시 실시간으로 움직입니다. 무엇이든지 여러분이 Smalltalk에서 명령을 내리기만 하면 그 명령에 객체는 즉시 반응합니다. 흡사 베이식(BASIC) 통역기의 환경을 보는 듯하겠지만 명령이 중심이 되는 베이식과 객체가 중심을 이루는 Smalltalk 는 하늘과 땅만큼 차이가 있습니다.


"Playground" 꾸러미의 "Circle", "Triangle", "Square" 등의 갈래는 지금 제가 알려드린 것보다 훨씬 많은 지시를 알아들을 수 있습니다. 아래에 이들 갈래들의 실체가 어떠한 지시를 알아들을 수 있는지를 정리해 놓겠습니다. 여러분이 일터에서 여러 개의 도형과 운동장을 만들어 보고, 이 객체들에게 지시를 내려보면 Smalltalk가 왜 객체지향 언어인지, 그리고 객체를 중심으로 생각한다는 것이 얼마나 편리한지를 느낄 수 있을 것입니다.

메서드 설명
side 도형의 면 수를 알아낸다. 세모의 경우에는 3을 남기고, 네모의 경우에는 4를 남기며, 동그라미의 경우는 무수히 많은 면을 가지고 있으므로 100을 남긴다.
sides: anInteger 지시를 받은 도형의 면을 결정한다. 면의 수에 따라 모양이 달라진다. 기본적으로 동그라미, 세모, 네모의 갈래가 있지만 이 지시를 이용해서 오각형 이상의 도형도 만들 수 있다.
radius 지시를 받은 도형의 반지름을 되돌려준다.
radius: anInteger 지시를 받은 도형의 반지름을 지정한다. 반지름의 길이에 따라 도형의 모양도 달라진다.
rotation 지시를 받은 도형이 현재 어떤 방향을 하고 있는지를 도(degree) 단위로 남겨준다.
rotation: anInteger 지시를 받은 도형의 방향을 anInteger 각도로 정한다.
position 도형이 운동장의 어디에 있는지를 좌표(Point)로 알려준다.
position: aPoint 도형의 위치를 aPoint로 정한다. (x@y)와 같은 방법으로 좌표를 얻는데, 모든 정수는 #@ 지시를 받아서 좌표를 만들 수 있기 때문이다.
color 현재 지시를 받은 도형의 색깔을 남긴다.
color: aColor 지시를 받은 도형의 색깔을 aColor로 정한다. Color 갈래에게 여러 가지 지시를 내려서 색을 만들어낼 수 있다.
growBy: anInteger 지시를 받은 도형을 anInteger 화소만큼 늘린다.
shrinkBy: anInteger 지시를 받은 도형을 anInteger 화소만큼 줄인다.
rotateBy: anInteger 지시를 받은 도형을 anInteger 각도만큼 회전시킨다.
moveBy: aPoint 지시를 받은 도형을 현재 거리에서 aPoint만큼 움직인다.
moveLeft: anInteger
moveRight: anInteger
moveup: anInteger
moveDown: anInteger
지시를 받은 도형을 상, 하, 좌, 우로 anInteger 화소만큼 움직인다.
chooseColor Windows에 있는 표준 색상표를 사용하여 사용자가 색을 선택할 수 있도록 한다.


운동장에서는 마음껏 뛰어 놀아도 나무랄 사람이 없습니다. 이제 여러분만의 운동장과 예쁜 도형들이 여러분들의 지시를 기다리고 있습니다. 이들도 Smalltalk의 객체이기 때문에 여러분이 원하는 지시를 모두 다 내려줄 수 있습니다. 위에서 설명한 지시를 이용해서 되도록 Smalltalk 객체에게 많은 지시를 내려보시기 바랍니다. 그리고 몸으로 많이 익혀서 적어도 이제부터는 Smalltalk 객체에게 지시를 내리는 일로 당황하는 일이 없도록 합시다.


물론 여러분이 객체에게 지시를 내릴 때 잘못하여 실수를 하는 경우도 있을지 모릅니다. 그러나 여러분이 그런 실수를 했다고 해서 Smalltalk 환경이 파괴되지는 않습니다. Smalltalk는 매우 안전하기 때문에 여러분이 어떤 실수를 해서 객체가 지시를 제대로 수행할 수 없었을 경우에는 언제든지 발자취(walk back) 창이 열립니다. 여러분은 이 상태에서 당황하지 말고 "Terminate" 단추를 눌러주기만 하면 됩니다. 그러면 발자취 창이 닫히고 원래의 상태로 되돌아옵니다.


이번 가름에서 소개한 운동장은 앞으로 Smalltalk의 여러 가지 다른 유용한 지시들을 설명할 때에도 사용될 것입니다. 그러니 지금부터 운동장을 만들고 없애는 방법과 그 안의 도형들이 어떤 지시를 알아듣는지, 또한 이러한 지시를 알아듣고 어떤 반응을 하는지에 익숙해지시기 바랍니다. 많은 연습보다 더 좋은 것은 없으니 이제 남은 것은 여러분들이 충분히 연습해서 익숙해지는 것입니다.


Smalltalk의 그래픽 환경


Smalltalk 환경은 그 환경을 만든 회사마다 독특한 특징을 가지고 있습니다. 대부분 Smalltalk 의 기본적인 부분은 같거나 비슷하지만, 특히 그래픽이나 멀티미디어 등을 처리하는데 있어서는 마땅히 정해진 표준이 없기 때문에 Smalltalk 개발 회사들은 자신만의 방법으로 이러한 것들을 구현해 내고 있습니다. 특히 Smalltalk를 이용해서 실무에서 사용해야 하는 응용 프로그램을 만들 경우에는 그것을 만들기 위한 뼈대(framework)가 있어야 하는데, 이런 뼈대들이 Smalltalk 개발사들마다 약간의 차이가 있습니다.


아무튼 필자는 공개용으로 사용할 수 있고, 또한 상용 시스템과도 호환성이 있는 Dolphin Smalltalk 의 뼈대를 따르기로 합니다. 어쩌면 다른 계열의 Smalltalk를 사용하는 여러분에게는 도움이 안 될지도 모르겠지만, 한 부분의 뼈대를 공부하고 난 뒤 다른 회사의 Smalltalk에서 작업을 해야할 때에는 보다 더 쉽게 접근할 수 있다고 생각합니다.


이번 마디에서 다루어진 운동장(Playground) 역시 Dolphin Smalltalk 의 MVP (Model-View-Present)의 뼈대를 사용한 작은 응용 프로그램입니다. 앞으로 여러분이 Smalltalk 를 깊이 공부하게 되면 Dolphin Smalltalk 의 응용 프로그램의 뼈대인 MVP 또한 공부하게 될 것입니다.


이번 마디에서 우리는 Smalltalk에서 창의 역할을 하는 운동장과 이 창에 표시될 도형들에게 직접 지시를 내려봄으로써 객체지향이란 무엇인지를 좀 더 쉽게 몸으로 느껴보고자 했습니다. 또한 앞에서 알아본 객체에게 지시를 내리는 방법 등을 되새겨 보는 시간을 가졌습니다. 여러분께서 "Circle", "Triangle", "Square" 라는 세 갈래에서 만들어진 객체들을 자유롭게 다룰 수 있다면, 여러분은 이미 Smalltalk의 문법과 의미를 통틀어 80% 이상을 공부한 것이나 나름이 없습니다. 힘을 냅시다. 다음에는 또 어떤 객체들이 여러분을 기다리고 있을지 모르니까요.


앗, 나의 실수!!!

어떤 일을 하다가 가장 위험한 때를 꼽으라면 여러분은 뭐라고 하시겠습니까? 저라면 "가장 익숙해졌을 때"라고 대답할 것입니다. 자신이 하고 있는 일, 알고 있는 지식에 익숙해졌다고 생각하면 처음에 가졌던 긴장감이 사라지고, 결국 이것은 '실수'를 낳는 법입니다.


Smalltalk 를 공부함에 있어서도 이는 비슷하다고 하겠습니다. 이제 어느 정도 Smalltalk에 대해서 익숙해 졌고, 객체에게 지시를 내리는 것도 나름대로 자신이 붙었다고 생각할 때 바로 '실수'라는 복병이 도사리고 있는 것 같습니다. 필자도 처음 Smalltalk를 공부할 때에 이렇게 '방심'하고 있다가 그 복병에게 된서리를 맞은 기억이 있습니다.


이 마디에서 여러분은 Smalltalk를 사용하다가 저지를 수 있는 여러 가지 실수들과, 그 실수들을 바로잡는 방법들에 대해서 알아보게 될 것입니다. 물론 Smalltalk가 여러분의 실수에 대해서 관대하게 대처하고 있기는 하지만, 큰 프로그램을 작성하는데 있어서 이러한 실수는 중대한 문제를 만들 수 있기 때문에 작은 실수 하나라도 용납해서는 안 될 것입니다. 그럼 먼저 전산과에 다니는 '김모군' 이야기부터 시작하도록 합시다.


'김 군'의 하루

4학년의 마지막 여름 방학. 김 군도 다른 전산과 학생들처럼 취업 준비에 여념이 없을 때였습니다. 그런데 안타깝게도 김 군은 뛰어난(?) 땡땡이 실력으로 수업을 한 날짜보다 땡땡이를 친 수업 일수가 더 많을 정도였습니다. 그러나 어찌 어찌해서 이제 졸업을 눈앞에 두고 있는데, 막상 취업을 하려 하니 김 군이 전산과에서 알고 있는 것이라고는 군대에 가기 전에 배운 포트란(FORTRAN)정도였습니다.


안절부절하고 있는 김 군을 옆에서 지켜보고 있던 친구는 김 군에게 취직자리를 알아봐 주겠다고 제의를 했고, 김 군은 마다할 처지가 아니어서 친구에게 신세를 지기로 했습니다. 김 군의 친구는 이제 막 인공위성에 대한 연구를 마치고 시제품을 개발하고 있는 "별누리"라는 회사에 김 군을 천거했습니다. 이력서 및 기타 다른 서류들을 준비하는 김 군을 보면서 친구는 귀띔을 해 두었습니다.


"이 회사는 새로 생긴 회사이기 때문에 객체지향에 대해서 관심이 많은 모양이더라구. 그러니깐 객체지향에 대해서 공부를 좀 해 두라고."


그 때부터 김 군의 객체지향 공부가 시작되었습니다. 그러나 처음부터 김 군은 난관에 부딪힙니다. 객체지향 책 한 권을 사서 앞의 몇 장을 읽어봤더니 거기에는 Object, message, class, instances, receiver, polymorphism, inheritance, encapsulation 등등 정말 머리가 터져 버릴 듯한 이상한 만들로 가득 차 있었습니다. 기가 막힐 노릇입니다.


하는 수 없이 김 군은 급한 김에 객체지향 프로그래밍 공부부터 먼저 시작하기로 하고, 친구들이 요즘 한참 인기가 좋다고 선전하는 C++를 공부하기 시작했습니다. 그러나 3년 전에 공부한 포트란과 C++는 너무나 달랐습니다. 하지만 김 군은 자신의 미래를 위해서 열심히 공부했습니다. 물론 시간이 많이 걸리는 일이었지만, 일단 취업을 해서 문제에 부딪히면 잘 할 수 있겠거니 하는 생각으로 열심히 했습니다.


뛰어난 말발과 쇼맨십으로 드디어 김 군은 "별누리" 회사에 취업을 하게 되었습니다. 김 군에게 처음으로 배당된 프로젝트는 인공 위성의 내부에 설치하게 될 탄소 막대(carbon steel rod)의 길이를 산출하는 것이었습니다. 팀장은 인공 위성 프로젝트가 Smalltalk 를 사용해서 개발되기 때문에 반드시 Smalltalk 를 사용해서 계산을 해야 한다고 으름장을 놓습니다. 그리고 다음과 같은 조건을 부여했습니다.

  • 인공 위성 내부의 최고 온도는 섭씨 500도이다.
  • 탄소 막대는 온도에 따라서 길이가 늘어나거나 줄어든다.
  • 인공 위성에 막대를 탑재했을 때 길이의 변화를 고려하여 공간을 만든다.


인공 위성이 태양열을 받게 되면 내부의 온도가 섭씨 500 도까지 올라가게 되는데, 탄소 막대 역시 온도가 올라가면 길이가 늘어나게 됩니다. 그렇기 때문에 섭씨 500도에서 탄소 막대의 길이가 얼마나 늘어나는지를 계산해서 충분한 공간을 마련해 두지 않으면 인공 위성이 파괴될 지도 모르는 일입니다. 이러한 사태를 막기 위해서 __"섭씨 500도일 때의 탄소 막대의 길이는 얼마나 늘어나는가"__를 해결하는 것이 김 군이 맡을 프로젝트인 것입니다.


그런데 벌써부터 눈앞이 캄캄합니다. 친구들은 객체지향 프로그래밍을 공부하려면 C++ 를 공부하라고 하는데, 이 회사는 왜 Smalltalk 를 사용하는 것일까요? 안 그래도 머리가 복잡한 김 군은 C++ 를 놔두고 왜 Smalltalk 으로 프로젝트를 수행하는지에 대해서 팀장에게 물었습니다.


"C++는 객체지향 언어가 아니라 객체지향을 추구하는 언어입니다. 우리는 유사품을 원하지 않습니다. 데드라인을 지키고 싶다면 복잡한 C++ 보다는 Smalltalk를 쓰는 게 좋을 겁니다."


팀장의 말에 김 군은 어찌할 바를 모르고 일단 몸으로 부딪히기로 했습니다.


먼저 김 군은 필요한 자료를 모으기 시작했습니다. 다행이 회사에서 오랜 실험을 통해 온도와 탄소 막대의 길이 변화에 대한 다음과 같은 공식을 도출했습니다.

s = s0(1 + Af)

- s0 : 화씨 0도일 때 탄소 막대의 길이. 단위는 inch.
- A  : 확장 계수(coefficient expansion) 알파<BR>= 6.7 * 10^-6.
- f  : 화씨 온도
- s  : 화씨 f도일 때의 실제 탄소 막대의 늘어난 길이


위의 공식에서 "s0"은 화씨 0도일 때의 막대의 길이를 말합니다. "A"(알파)는 확장 계수인데, 이는 화씨 1도가 증가할 때 금속의 길이가 얼마만큼 늘어날지를 나타내는 상수입니다. 그리고 "f" 는 구하려고 하는 화씨 온도(fahrenheit degree)입니다.


김 군에게 떨어진 자료는 이것입니다. 이제 이 공식을 이용해서 섭씨 500 도일 때의 막대의 길이를 알아내어야 하는 것입니다. 그런데 문제가 있습니다. 위의 공식은 섭씨 온도(centigrade degree)가 기준이 아니라 화씨 온도가 기준이 되는 것입니다. 따라서 공식을 적용하기 전에 우선 섭씨 온도를 화씨 온도로 바꾸어주는 작업이 필요합니다.


신입 사원인 김 군, 잘 몰라서 버벅거리지만 열심히 하면 회사에서 잘리지는 않겠지 라는 생각으로 늦게까지 야간 작업을 하기로 결심하고, 과학 백과 사전을 뒤진 끝에 드디어 섭씨 온도를 화씨로 바꾸는 아래와 같은 공식을 찾아내었습니다.

          9
 f = 32 + --- c
          5

        - c : 섭씨 온도
        - f : 화씨 온도


이제 김 군은 앞이 캄캄합니다. 화씨 온도로 된 탄소 막대의 길이 구하는 공식과 섭씨 온도를 화씨 온도로 바꾸는 공식을 얻기는 했지만, 이제 이것을 Smalltalk 의 글토막으로 써내야 하는 것입니다. 시간은 벌써 새벽 다섯 시. 점점 밝아오는 여명을 보면서 김 군은 책상 위에 무거운 머리를 내려놓습니다. 스르르 감기는 눈과 함께 김 군의 의식은 점점 흐릿해져만 가는데....


실수, 또 실수

누군가 김 군의 몸을 흔드는 바람에 벌떡 머리를 들어 봅니다. 시간은 벌써 여덟 시 반, 김 군은 오늘까지 Smalltalk 글토막을 제출해서 팀장에게 검사를 받아야 합니다. 그런데 남은 시간은 30분. 자신을 깨워준 동료에게 감사하다는 말을 전한 뒤 김 군은 이제 Smalltalk 시스템을 실행합니다.


머리가 복잡합니다. 그러나 일단 한 가지 한 가지씩 문제를 해결하면 길이 보일 듯도 합니다. 먼저 김 군은 섭씨 온도를 화씨 온도로 바꾸는 공식부터 Smalltalk를 사용해서 짜기로 했습니다.

          9
f = 32 + --- c
          5


일단 Smalltalk에서 분수는 "/"를 써서 나타내야 합니다. 그러므로

f = 32 + 9/5 c           (1)


로 바꿀 수 있습니다. 그러나 (1)의 경우는 문제가 있습니다. Smalltalk를 사용해서 계산을 할 때에는 연산수와 연산자가 알맞게 배치되어야 합니다. 그런데 위의 경우 "9/5"와 "c" 사이에는 아무런 연산자가 없습니다. 따라서 이 위치에 적당한 연산자를 넣어줄 필요가 있습니다.


수학에서 식을 세울 때 곱셈 연산자를 사용하지 않습니다. 그러므로 위의 계산식은 곱셈 연산자를 사용해서 아래와 같이 고칠 수 있습니다.

f = 32 + 9/5 * c                 (2)


자, 그럼 이제 공식 하나가 완성된 것일까요? 물론 일반적인 경우라면 위의 공식에는 이상이 없을 것입니다. 그러나 김 군은 Smalltalk를 사용해야 합니다. 앞에서 "길수(method)의 우선 순위"에 대해서 공부한 바와 같이, Smalltalk에서는 __겹마디 지시(binary message)는 무조건 왼쪽에서 오른쪽으로 가늠된다__는 규칙이 존재합니다. 그러므로 위의 식은 먼저 "32 + 9" 가 가늠되어버립니다. 따라서 "9/5"를 제대로 분수로 만들어 주기 위해서는 괄호를 사용해서 식을 고칠 필요가 있습니다.

f = 32 + (9/5) * c               (3)


그런데 여기서 끝난 것이 아닙니다. 공식에서는 분명히 덧셈보다 곱셈이 먼저 게산 되어야 합니다. 즉 "(9/5)" 와 "c" 를 곱한 뒤에 "32"를 더해야 하므로, 역시 (3)에 괄호를 하나 더 둘러주어서 연산의 우선 순위를 명확히 해 줍니다.

f = 32 + ((9/5) * c)             (4)


그런데 (4)의 공식이 Smalltalk에서 실행된다면 어차피 "9/5"를 둘러싸고 있는 괄호는 필요가 없습니다. 왜냐하면 길수의 우선 순위에 따라서 "/" 길표(selector)가 "*" 길표보다 먼저 가늠될 것이기 때문입니다. 그래서 다음과 같이 계산식을 줄일 수 있습니다.

f = 32 + (9/5 * c)               (5)


완성입니다. 이제 섭씨 온도를 화씨로 바꾸는 일은 모두 끝난 셈입니다. 다음으로 탄소 막대의 길이를 구하는 공식을 Smalltalk 글토막으로 만들어 봅시다.

s = s0(1 + Af)


회사의 자료에 따르면 "s0"의 값은 30이라고 했으므로, 다음과 같은 글토막을 만들 수 있습니다.

s = 30 * (1 + (6.7e-6*f))


위의 글토막 역시 공식에서 생략된 곱셈 연산자를 붙여주고 연산의 우선 순위를 올바르게 바꾸어주기 위해서 각 항에 괄호를 둘러주었습니다. 이제 위의 두 개의 글토막을 하나로 합쳐서 바탕글을 만들면 아래의 바탕글 1 과 같이 될 것입니다.


바탕글 1::탄소막대 길이 구하기 (1)

c = 500
f = 32 + (9/5 * c)
s = 30 * (1 + (6.7e-6*f))


바탕글에서는 "c"라는 변수를 두어서 섭씨 온도 500도를 나타내도록 했습니다. 이제 김 군은 위의 바탕글 1 을 가늠하기 위해서 일터(workspace)를 열고 글토막을 입력한 뒤 떨리는 손으로 글토막을 펴 봅니다. 잠시 후, 김 군은 경악하고 맙니다! 오호, 통제라~!


도대체 김 군이 작성한 바탕글 1에 어떤 문제가 있는지 우리도 직접 바탕글에 덩이(block)를 씌운 뒤 Workspace > Display It 명령을 실행하거나 Ctrl-D 글쇠를 눌러서 글토막을 펴 봅시다. 그러면 다음 그림 3-17, 3-18과 같은 결과를 얻을 수 있을 것입니다. 이 그림을 보게 되면 어째서 왜 김 군이 경악을 금치 못했는지를 알 수 있을 것입니다.


그림 3-17::바탕글 1 을 가늠하기 위해 덩이를 씌운 모습 (Stp3-17.gif)


그림 3-18::"undeclared" 잘못 발생 (Stp3-18.gif)


그림 3-18 을 보면 멀쩡하던 바탕글이 갑자기 붉은 색으로 바뀌어 있고, "c" 라는 글자에 덩이가 씌워져 있습니다. 번역기가 바탕글을 번역할 때 "c" 때문에 문제가 있다고 알려주는 것입니다. 도대체 어떤 문제가 있는 것일까요? 잠시 일터 아래에 있는 상태 표시줄을 살펴보면 역시 붉은색의 금지표시와 함께 다음과 같은 글이 씌여 있음을 보게 될 것입니다.

Error: undeclared 'c'


잘못(error)이 있답니다. "undeclared"란 말은 "선언되지 않았다"는 말이기 때문에, 위의 문장은 "'c'라는 것을 선언하지 않아서 잘못이 생겼습니다"라는 뜻입니다. 자, 이제 김 군이 무슨 실수를 했는지 아시겠습니까?


Smalltalk 에서 어떤 객체에 이름을 붙이거나 계산 결과로 만들어진 객체를 묶어두기 위해서는 꼬리표(variable)를 붙어야 한다고 했습니다. 우리의 바탕글에서 꼬리표믄 c, f, s 세 개가 쓰였습니다. 그런데 Smalltalk에서 꼬리표를 쓰기 전에는 언제나 __선언을 먼저 해 주어야__ 한다고 설명한 바 있습니다. 물론 일터 꼬리표(workspace variable)나 큰(global) 꼬리표의 경우 사용하는 것 자체가 바로 꼬리표를 선언하는 것이지만, 우리는 덩이를 씌운 글토막 안에서 꼬리표를 사용해야 하기 때문에, 작은 꼬리표(temporary variable)를 써야 합니다.


작은 꼬리표를 선언하기 위해서는 글토막 윗부분에 세로 막대 두 개를 세우고 이 막대 사이에 바탕글에서 쓸 작은 꼬리표의 이름을 적어주면 되는 것입니다. 우리의 경우는 c, f, s, 세 개의 꼬리표를 사용할 것이므로 아래와 같이 써 줄 수 있습니다.

| c f s |


이렇게 하면 작은 꼬리표 세 개가 생기고 이 꼬리표에 여러 가지 객체를 붙일 수 있습니다. 뒤늦게 이런 사실을 깨달은 김 군이 수정한 바탕글 2 를 살펴보도록 합시다.


바탕글 2::탄소막대 길이 구하기 (2)

| c f s |
c = 500
f = 32 + (9/5 * c)
s = 30 * (1 + (6.7e-6*f))


이제 더 이상의 문제가 생기지 않기를 바라면서 김 군은 바탕글 2 를 가늠합니다. 그러나... 산 넘어 산입니다. 그림 3-19 를 보십시오.


그림 3-19::"SmallInteger does not understand #f" (Stp3-19.gif)


바탕글 2 를 가늠해 보면 그림 3-19 와 같이 발자취 창(walkback window)이 나타납니다. 시간은 이제 8시 40분. 큰일입니다. 마감 시간은 다가오는데 자꾸만 문제가 생기다니.... 발자취 창의 제목 표시줄을 보면 "SmallInteger 는 #f 를 이해할 수 없습니다" 라는 말이 씌여 있습니다. 즉 이 말은 "SmallInteger 갈래에 딸린 어떤 객체가 있는데, 이 객체는 #f 라고 하는 지시를 알아들을 수 없습니다" 라는 말입니다. 그럼 도대체 SmallInteger 갈래의 실체와 #f 라는 지시의 정체는 무엇일까요?


이런 문제가 생길 때일수록 문제 자체에 집착하면 낭패를 봅니다. 잠시 다른 각도에서 생각을 해 봅시다. 우리는 바탕글 2 에서 SmallInteger 실체에게 'f'라는 지시를 보낸 일이 __결코 없습니다__. 그런데 Smalltalk 는 바탕글 2 를 SmallInteger 실체에게 f 라는 지시를 보냈다고 이해하고 있습니다. 여기서 유심히 볼 것은 바로 "f" 입니다. "f" 는 우리가 작은 꼬리표로 쓰기로 한 것입니다. 그런데 바탕글을 보면

c = 500
f = 32 + (9/5 * c)


와 같은 부분이 있습니다. 잘 보면 "f" 앞에 500이라는 숫자가 있습니다. 그러고보니 500 은 SmallInteger, 즉 작은 정수 갈래의 실체입니다. 그럼 500 에게 'f'라는 지시가 내려졌다는 말인데....


좀 더 확실히 알아보기 위해서 바탕글 2 의 모양을 좀 바꾸어서 바탕글 3 으로 만들어 보았습니다. 일단 열려 있는 발자취 창을 "Terminate" 단추를 눌러서 닫은 뒤에 아래 바탕글을 유심히 살펴봅시다.


바탕글 3::탄소막대 길이 구하기(3)

| c f s | c = 500 f = 32 + (9/5 * c) s = 30 * (1 + (6.7e-6*f))
              ~~~~~


Smalltalk 에서 바탕글을 쓸 때에는 줄을 바꾼다거나 빈칸을 얼마큼 넣는다거나 하는 것이 문제가 되지 않습니다. 따라서 위와 같이 여러 개의 문장을 한 줄에 늘어놓아도 실행에는 아무런 지장이 없다는 말인데....


위에서 밑줄 친 곳을 잘 살펴보십시오. 여러 줄의 바탕글을 한 줄로 만들어 놓고 보니 이제야 문제가 보입니다. 위와 같이 해 놓으면 당연히 "500"에 'f' 라는 지시를 보내는 꼴이 됩니다. 이게 말이 됩니까? 당연히 작은 정수 500은 'f'라는 지시에 대해서 반응할 길수가 없기 때문에 발자취 창을 표시하고 만 것입니다.


자, 그럼 어떻게 해야할까요? 지금까지의 바탕글에서 잘못된 것이 무엇이겠습니까? 네, 바로 마침표(.)입니다. Smalltalk에서 한 문장이 끝난 뒤에는 반드시 마침표가 필요합니다. 그러나 지금까지는 한 문장이 끝난 뒤에 마침표를 찍지 않았습니다. 이제 김 군도 원인을 알았으니 바탕글 3 에 마침표를 붙여서 아래의 바탕글을 만들었습니다.


바탕글 4::탄소막대 길이 구하기 (4)

| c f s | c = 500. f = 32 + (9/5 * c). s = 30 * (1 + (6.7e-6*f)). s.


위에서 각 문장이 끝날 때 마침표를 찍어주었습니다. 그리고 맨 마지막에 붙임 "s."는 위의 바탕글에서 계산한 결과를 좀 더 확실히 보여주기 위해서 넣은 것입니다. 어떤 객체나 객체가 붙어 있는 꼬리표를 있는 그대로 펴면 그 내용이 그대로 일터에 나타남을 기억하고 계시겠지요?


자, 이제 끝이 눈에 보입니다. 위의 바탕글 4 를 다시 원래대로 한 줄에 한 명령이 들어갈 수 있도록 바탕글 5 로 모양을 바꾸어 봅시다. 아무래도 한 줄에 여러 가지 명령이 있는 것보다는 한 줄에는 한 개의 명령만 있는 것이 바탕글을 읽는 데에도 도움이 됩니다.


바탕글 5::탄소막대 길이 구하기 (5)

| c f s |
c = 500.
f = 32 + (9/5 * c).
s = 30 * (1 + (6.7e-6*f)). 
s.


바탕글이 훨씬 알아보기 쉬워졌습니다. 이렇게 만들어진 바탕글 5 에 덩이를 씌워 가늠해 봅시다. 이제부터 김 군의 문제는 우리의 문제도 된 것입니다. 이번에는, 정말 이번에는 되겠지요? 힘을 내고 바탕글 5를 펴 봅시다.


그림 3-20::UndefinedObject does not understand #multiplyByFraction: 잘못 발생 (Stp3-20.gif)


으악~! 필자도 경악하고 맙니다... --: ....


또다시 발자취 창이 나타납니다. 그런데 이번에는 설상가상으로 쓰지도 않은 갈래의 실체와 듣도 보도 못한 길표가 불쑥 튀어나왔습니다. 도대체 UndefinedObject 는 어디서 튀어나왔고, #multiflyByFraction:은 뭘 하는 길표인지, 답답하기만 합니다. 어디서부터 시작해야할지 모르겠습니다.... --: 이제 시간은 10분밖에 남지 않았는데....


분명히 답이 있습니다. 답은 바로 발자취 창에서 찾아야 합니다. 발자취 창에는 문제가 발생하기까지 어떤 길수들을 사용했는지에 대한 발자취가 나타나 있습니다. 우리는 이 발자취를 되짚어 따라감으로써 문제의 원인을 알아낼 수 있습니다. 보통 발자취 창에 표시된 제목만 보면 어떤 잘못인지를 금방 이해할 수 있지만, 지금과 같은 경우는 발자취 그 자체가 도움이 됩니다.


발자취 창에 표시된 발자취를 아래에 적어봅니다. 발자취를 되짚어 따라가기 위해서는 아래에서부터 위로 올라오면서 읽어야 한다는 것을 잊지 마십시오.

UndefinedObject(Object)>>doesNotUnderstand:
Fraction>>*
UndefinedObject>>{unbound}doIt
CompiledExpression>>value:
SmalltalkWorkspace>>evaluateRange:ifFail:
SmalltalkWorkspace>>displayIt
...(이하 생략)...


위의 발자취를 보면 Smalltalk의 일터에 있는 displayIt이란 길수가 처음 사용되었음을 볼 수 있습니다. 우리가 글토막에 덩이를 씌운 뒤 Ctrl-D 를 사용하여 글토막을 가늠하면 이와 비슷한 형식의 발자취가 생깁니다. 아무튼 어찌어찌해서 발자취를 따라가다 보면 다음의 두 줄을 발견할 수 있습니다.

UndefinedObject(Object)>>doesNotUnderstand:
Fraction>>*


열심히 바탕글이 실행됩니다. 그러다가 Fraction 갈래의 실체, 즉 분수 객체에게 #* 지시가 내려집니다. 그런데 문제는 이 다음줄, 즉 UndefinedObject 에게 #multiplyByFraction 이라는 지시가 내려졌다는 것입니다. 우리는 바탕글에서 UndefinedObject를 쓴 적도 없고, #multiplyByFraction이라는 길수를 사용한 적도 없습니다. 정말 환장할 노릇입니다. 그러나 아무튼 분수 객체에 #* 지시가 보내지면서부터 문제가 시작되는 것으로 생각해야 좋을 것입니다.


바탕글에서 Fraction, 즉 분수 객체는 "9/5"인데, 이 분수가 사용된 부분을 아래에 보이겠습니다.

f = 32 + (9/5 * c).


위의 글토막을 길수의 우선 순위를 생각하면서 따라가 봅시다. 먼저 괄호가 쳐 진 "(9/5 * c)"가 계산될텐데, "9/5"의 #/ 지시가 먼저 실행될 것입니다. 그러면 이제 "9/5"는 엄연한 분수 객체가 됩니다. 이제 이 객체에게 #* 지시가 내려지는데, #* 지시는 겹마디 지시이므로, #* 길표 오른쪽에 있는 객체를 인자(parameter)로 취합니다. 여기서 #*, 즉 곱셈 연산자의 인자는 바로 "c"가 됩니다. 그런데 바탕글에 의하면 "c" 꼬리표는 500 에 붙어 있어야 합니다. 그런데 여기서 문제가 생긴다는 것은 결국 "c"라는 꼬리표에 어떤 문제가 있다는 것으로 생각할 수 있습니다. 도대체 "c" 꼬리표는 어떤 객체에 붙어 있을까요?


바탕글 5 에서 다음의 두 줄에만 더이를 씌운 후 펴 봅시다.

| c f s |
c = 500          false


그림 3-21::"false"를 결과로 넘긴 화면 (Stp3-21.gif)


어라? "거짓"(false)? 뭐가 거짓이라는 말이지? 이해가 안 갑니다. "c" 라는 꼬리표에 500을 붙였으니 당연히 꼬리표에 붙인 500이라는 객체가 그대로 결과로 나타나야 하는데, '거짓'이라니... 뭔가 잘못되어가고 있는 듯 합니다. 객체를 꼬리표에 붙였는데 왜 거짓이라는 결과가 나올까..... 객체를 꼬리표에 붙여? __붙인다__... 붙여..? 그래! 붙임표!


네! 이제 문제를 알아 낸 것 같습니다. Smalltalk에서 꼬리표에 어떤 객체를 붙이기 위해서는 붙임표를 써야 하는데, 그 붙임표가 바로 ":="입니다. 반면 "="은 두 객체가 같은지 다른지를 비교하는 길표였습니다.


결국 위의 바탕글은 c와 정수 500을 비교하는 것인데, 꼬리표 c 가 붙어 있는 객체와 500이 서로 달랐기 때문에 거짓(false)을 넘겼을 것입니다. 그럼 꼬리표 c는 어디에 붙어 있을까요? 바탕글에서 다음 까지만 덩이를 씌워서 가늠해 보십시오.

| c f s |
c        nil
그림 3-22::c 꼬리표는 아무것에도 붙어 있지 않았다! (Stp3-22.gif)


그림에서 보듯이 c 꼬리표에는 아무 것도 붙어 있지 않았습니다. 'nil' 이란 "헛것, 비어있는 것, 없는 것" 을 나타내기 때문에, 결국 c꼬리표는 아무런 객체에도 붙어 있지 않았던 것입니다. 결과적으로 헛(nil)과 정수 500을 서로 비교하라고 했으니 거짓(false)이 나올 수 밖에요.


자, 그럼 이제 바탕글에 있는 모든 "="을 붙임표인 ":="으로 바꾸어야 하겠지요? 이렇게 해서 수정된 것이 <바탕글 6>입니다.


바탕글 6::탄소막대 길이 구하기 (최종판)

| c f s |
c := 500.
f := 32 + (9/5 * c).
s := 30 * (1 + (6.7e-6*f)). 
s.


[실행 결과]
☞ 30.187332


고국에 계신 동포 여러분, 기뻐해 주십시오! 드디어 바탕글이 제대로 돌아갑니다. 계산기를 사용해서 검산을 해 봐도 역시 똑같습니다. 결과가 맞게 나타납니다. 이렇게 기쁠 수가! 드디어 해 냈습니다!


검산을 끝내자마자 시계는 아침 아홉 시를 가리킵니다. 때마침 팀장이 김 군의 책상을 방문합니다.

"다 됐으면 실행 한 번 해 보세요."


굵은 팀장의 목소리가 들립니다. 김 군은 자신 있게 바탕글을 가늠해서 결과를 출력해 보였습니다. 팀장은 만족한 듯 했습니다.


"아주 잘 했군요. 그럼 섭씨 100도일 때 막대의 길이를 구할 수 있겠어요? 지금 바로 구해 보세요."


김 군은 처음부터 "c"라고 하는 꼬리표를 사용했기 때문에 여러 가지 온도에 따른 결과를 출력하는 것은 식은 죽 먹기였습니다. 단순하게 "c" 꼬리표에 500 대신 100 이라는 객체만 바꾸어 붙여주고 바탕글을 펴면 되는 일이었습니다. 아래 그림 3-23 를 보면 오른쪽에 결과가 나타납니다.


그림 3-23::여러 가지 온도에 대한 결과 출력 (Strp3-23.gif)


"아주 좋군요. 프로그램을 조금만 수정해도 결과가 쉽게 출력되니까 참 편리합니다. 수고했어요. 하지만 바탕글이 읽기가 좀 어렵군요."


김 군은 드디어 첫 번째 프로젝트를 끝냈습니다. 부스스한 머리, 푸석푸석한 얼굴, 벌게진 눈, 부르튼 입술. 빤질빤질했던 김 군의 평소 모습과는 완전히 다른 모습으로 바뀌어 있었지만, 김 군은 무언가를 이루어냈다는 기쁨이 컸습니다. 더구나 팀장의 칭찬까지 받았으니... 그러나 팀장의 마지막 한 마디가 앙금이 되어 남습니다.


"하지만 바탕글이 읽기가 좀 어렵군요."


무슨 말일까... 바탕글을 쓸 때에도 읽기 쉽게 쓰는 법이 있을까? 이 생각 저 생각을 하고 있는 사이, 바로 옆에서 오늘 김 군을 깨워 준 동료가 한 마디 합니다.


"자네, 오늘 내가 깨워줘서 살았어. 그러니까 술 한잔 사야지?"


기독성과 효율성

'바탕글을 읽기 쉽게 짜라는 말이 무슨 소리일까?'


소주 몇 잔에 거나해진 김 군은 집으로 돌아오면서 이런 생각을 곱씹어 봅니다. 술자리에서 친구는 '프로그램을 만들기 위해서 쓰는 바탕글(source code)도 엄연히 '글'이기 때문에 사람이 읽기 쉬워야 한다'는 생각을 김 군에게 피력했습니다. 아울러 그는 다음과 같은 말을 짖지 않았습니다.


"바탕글이 읽기 쉬워야 하는 것도 중요하지만, 프로그램의 효율성도 함께 생각해야 돼. 자네가 짠 바탕글은 효율성도 그리 좋지 않았어."


물론 친구는 김 군을 생각해서 한 말이었지만 왠지 모르게 김 군의 어깨는 축 처지기만 합니다. 열심히 했는데, 그래서 결과도 좋았는데, 막상 자신이 짠 바탕글이 읽기도 어려웠고 바탕글의 효율성도 떨어진다는 말을 들으니 의기소침 해 진 것. 이제 집으로 걸어가는 김 군을 위로해 줄 사람은 바로 이 글을 쓰고 있는 저와 제 글을 읽고 있는 여러분입니다. 자, 김 군의 바탕글이 왜 일기도 어렵고 효율적이지도 못한지에 대해서 차근차근 살펴보고, 김 군에게 따스한 조언이라도 한 마디 해 주면 좋겠지요?


프로그램을 작성하는 사람들은 '어떻게 하면 프로그램이 효율적으로 실행될까'라는 문제에 대해서 고민을 하곤 합니다. 물론 이제 막 Smalltalk를 공부하고 있는 여러분들이 프로그램의 효율성에 지나치게 얽매일 필요는 없습니다. 그러나 효율성이란 무엇이며, 이것이 왜 필요한지, 그리고 어떻게 하면 프로그램의 효율성을 높일 수 있는지에 대해서 알아두는 것은 필요한 일이라고 생각합니다.


효율성(efficiency)이란 가장 적은 수의 명령어와 기억공간을 사용하여 일을 처리하는 것을 말합니다. 그러니까 똑같은 일을 하더라도 어떤 사람은 이것저것 부산하게 많은 일을 하는 사람이 있는가 하면, 또 다른 사람은 꼭 필요한 일만 하는 경우가 있는데, 이 두 가지 경우 중에 뒤의 경우가 효율성이 좋다고 볼 수 있습니다.


그럼 김 군의 바탕글을 다시 한 번 살펴보면서 왜 효율성이 떨어지는지를 살펴보도록 합시다.


바탕글 6::탄소막대 길이 구하기 (최종판)

| c f s |
c := 500.
f := 32 + (9/5 * c).
s := 30 * (1 + (6.7e-6*f)). 
s.


이 바탕글에서는 정수(SmallInteger), 분수(Fraction), 소수(Float)의 세 개의 객체가 사용되고 있습니다. 그런데 "2.2.6. 고급 셈! 셈! 셈!"에서 분수와 소수를 정수와 섞어 쓸 경우에는 계산이 이루어지기 전에 일단 연산수가 소수로 바뀐다고 이야기한 적이 있습니다. 그러면 위의 바탕글이 계산될 때에는 어떤 변화가 일어나는지를 살펴봅시다.

| c f s |
c := 500.                          c := 정수
f := 32 + (9/5 * c).               f := 정수 * 분수 * 정수
s := 30 * (1 + (6.7e-6*f)).        s := 정수 * (정수 + (소수*분수)).
s.                                 소수


먼저 처음 꼬리표 "c"에 정수값이 대입됩니다. 그 다음에 아래의 문장이 실행됩니다.

f := 32 + (9/5 * c).
           ~~~


그런데 위의 경우 계산식에 분수 "9/5"가 있기 때문에 계산되기 전에 모든 연산수가 일단 분수로 바뀝니다. 그래서 결과적으로 꼬리표 "f" 에는 분수값이 들어갑니다. 물론 운이 좋아서 계산 결과가 정수로 약분이 되면 다행이겠지만, 대부분의 경우 "f" 꼬리표에는 분수값이 들어갑니다.


이제 "s" 꼬리표에 값을 계산하여 넣기 위해 다음의 문장이 실행됩니다.

s := 30 * (1 + (6.7e-6*f)).
                ~~~~~~


이 계산식은 정수, 분수, 소수를 모두 포함하고 있습니다. 따라서 모든 연산수가 소수로 바뀌어 계산이 이루어집니다. 결과적으로 바탕글 6 은 계산이 수행될 때마다 여러 객체들이 정수에서 분수나 소수로 바뀌게 됩니다. 정수에서 분수로 변환되는 것은 별로 어렵지 않지만, 어떤 객체가 소수(Float)로 바뀔 때에는 생각보다 비싼 대가를 치러야 합니다. 우리 눈에는 보이지 않지만 "(9/5)" 가 "1.8" 이라는 소수로 바뀌기까지는 족히 수십 개의 명령이 수행되어야 합니다.


따라서 프로그램에서 소수(float)를 사용하는 계산식이 있을 때에는 서로 다른 갈래의 객체들이 소수로 변환되기 때문에 걸리는 시간을 최소화할 필요가 있습니다. 그래서 다음과 같이 바탕글을 고칠 수 있습니다.


바탕글 7::탄소막대 길이 구하기 (소수 사용)

| c f s |
c := 500.0.
f := 32.0 + (1.8 * c).
s := 30.0 * (1.0 + (6.7e-6*f)). 
s.


바탕글 7 을 살펴보면 식에 사용된 모든 객체가 소수뿐임을 알 수 있습니다. 이렇게 하면 객체들 사이의 변환 과정이 필요없기 때문에 좀 더 효율적인 바탕글을 만들 수 있습니다.


그런데 잘 보면 바탕글 7 은 효율성은 뛰어날지 몰라도 글을 읽기가 오히려 바탕글 6 보다 어렵습니다. "30"과 같이 정수로 표시해도 될 것을 효율성 때문에 "30.0"과 같이 썼기 때문입니다. 이렇게 효율성을 중시하면 바탕글의 기독성이 떨어질 수 있습니다.


앞에서 '효율성'이란 수행되는 명령의 수가 적어야 하고 또한 __기억 공간을 적게 사용하는__ 것이라고 말한 바 있습니다. 이렇게 본다면 지금까지 우리가 Smalltalk 에서 사용하는 꼬리표(variables)도 기억 공간을 차지합니다. 따라서 바탕글을 쓸 때 아예 꼬리표를 쓰지 않게 되면 꼬리표 때문에 들어가는 기억 공간을 줄일 수 있습니다. 다음 글토막을 살펴봅시다.

30 * (1 + (6.7e-6 * (32 + (9/5 * 500))))     30.187332


위의 글토막은 꼬리표를 전혀 쓰지 않고 만들어본 글토막입니다. 바탕글 7 과 같이 소수점으로 식을 고치게 되면 다음과 같이 되겠지요?

30.0 * (1.0 + (6.7e-6 * (32.0 + (1.8 * 500.0))))  30.187332


위의 글토막은 꼬리표를 전혀 쓰지도 않았고, 모든 객체를 소수로 바꾸어 표현했습니다. 따라서 Smalltalk 입장에서 보면 매우 효율성이 뛰어난 것임에 분명합니다. 그런데, 위의 두 개의 글토막을 가만히 살펴봅시다. 이 글토막이 탄소막대를 구하는 것이라고 누가 생각할 수 있겠습니까? 도대체 뭘 하는 바탕글인지 알 수가 없습니다. 만약 여러분이 위와 같은 글토막을 써 놓고 일 주일 쯤 푹 쉬었다가 다시 위의 글토막을 문득 발견했다고 생각해 보십시오. 과연 그 때도 여러분은 저 글토막이 '탄소 막대 구하는 글토막'이라는 사실을 깨달을 수 있겠습니까?


자, 그럼 위에서 만든 글토막을 가지고 섭씨 100 도일 때의 탄소 막대의 길이가 얼마인지 구한다고 해 봅시다. 위의 글토막에서 무엇을 고쳐야 할지 금방 보고 알 수 있을까요?


이런 경우도 생각해 봅시다. 만약 '별누리' 회사가 때돈을 벌어서 탄소 막대 대신 전도율이 훨씬 좋은 백금으로 막대를 만들었다고 해 봅시다. 그럼 이번에는 탄소 막대가 아니라 백금 막대의 길이를 측정해야 합니다. 섭씨 500 도일 때 백금의 길이를 구하려고 한다면 위의 글토막을 어떻게 고쳐야 하겠습니까? 여러분이 직접 해 보십시오. 열이면 열, 대부분의 사람들이 저런 숫자들만 나열된 글토막을 붙들고 씨름하고 있을 게 뻔합니다.


자, 위의 글토막이 정말로 효율성이 있어 보입니까? 물론 컴퓨터 입장에서 보면 효율성이 좋은 글입니다. 그러나 위의 글토막은 사람이 알아보기가 너무 어렵습니다. 따라서 효율성에서 바탕글의 기독성(readability)을 생각하지 않을 수 없게 되는 것입니다.


그럼 여기서 우리는 고민을 해봐야 합니다. 효율성이냐, 바탕글의 기독성이냐. 저는 이 두 가지 중에 하나를 고르라고 한다면 바탕글의 기독성을 택하겠습니다. 왜냐하면 어떤 프로그램이라도 한 번 만들어지면 나중에 분명히 고쳐야 할 때가 있기 때문입니다.


프로그램을 고치려면 자신이 무슨 생각으로 바탕글을 썼는지를 알아볼 수 있어야 하는데, 효율성만 중시한 나머지 바탕글을 암호와 같이 짜 놓았다면, 나중에 어떻게 고치겠습니까? 따라서 저는 아래와 같은 말을 여러분에게 해 드리고 싶습니다.


"바탕글은 한 번 씌여지고 여러분 읽혀진다."


따라서 바탕글이 길어지더라도 읽기 쉽게 만들어야만 나중에 고치기가 쉽습니다. 물론 지금까지 우리들이 만들어온 바탕글은 길어봐야 열 줄이 체 안 되는 것들이었습니다. 그러나 여러분이 나중에 큰 프로그램을 짜게 될 때 바탕글을 읽기 쉽게 쓰지 않으면 프로그래밍 자체가 불가능해집니다. 따라서 바탕글을 쓸 때 효율성보다는 기독성에 초점을 맞추어 작성해야 한다고 필자는 생각합니다. 일단 바탕글을 읽기 쉽게 쓰고 난 다음 좀 더 높은 효율성이 필요하면 그 때 가서 바탕글을 고치면 됩니다. 그래야만 프로그램을 만들어 놓고 필요할 때마다 고쳐서 쓸 수 있습니다.


그러면 바탕글을 읽기 쉽게 만들기 위해서는 어떻게 해야 할까요? 답은 바로 "이름을 잘 짓는 것"입니다.


프로그램을 짜다 보면 이름을 지어야 할 때가 많이 있습니다. 대표적으로 꼬리표의 이름을 지을 때입니다. 앞에서 김 군은 세 개의 꼬리표를 "c, f, s" 라는 이름으로 지었습니다. 그런데 이렇게 꼬리표를 붙이는 것은 바탕글을 매우 읽기 어렵게 만듭니다. 물론 수학에서 변수를 쓸 때에는 a, b, x, y 와 같이 이름을 붙입니다만, 프로그래밍은 수학이 아닙니다. 프로그래밍은 어디까지나 사람의 생각을 나타내야 하기 때문에 수학 공식에서처럼 이름을 짧게 붙여서는 안됩니다.


이름을 지을 때에는 그 이름이 __내용을 충분하게 설명할 수 있도록__ 지어야 합니다. 다시 말해서, 이름만 척 보면 그게 어떤 것인지를 알 수 있어야 한다는 말입니다. 만약 여러분이 인공위성에 대한 지식이 전혀 없는 상태에서 김 군이 만든 바탕글 6 을 본다면 도대체 'c, f, s'가 무엇을 의미하는지 알 수 있겠습니까?


바탕글을 읽기 쉽게 만들기 위해서 이름은 최소한 한 개 이상의 의미 있는 단어를 사용해서 짓도록 하십시오. 아래 바탕글 8 을 보십시오.


바탕글 8::탄소막대 길이 구하기 (꼬리표 이름 변경)

| centigrade fahrenheit length |
centigrade := 500.
fahrenheit := 32 + (9/5 * centigrade).
length := 30 * (1 + (6.7e-6 * fahrenheit)). 
length.


"c, f, s"로 되어 있던 꼬리표의 이름을 각각 '섭씨'(centigrade), '화씨'(fahrenheit), '길이'(length)를 나타낼 수 있도록 이름을 고쳤더니 읽기가 훨씬 쉬워졌습니다. 물론 'centigrade'나 'fahrenheit'과 같은 단어들이 생소한 사람에게는 자칫 바탕글이 더욱 어렵게 보일 수 있겠지만, 각 단어의 의미가 무엇인지 파악하면 위의 바탕글을 읽기는 훨씬 쉬워질 것입니다.


위의 예와 같이 바탕글에서 어떤 수식을 쓸 경우 숫자 자체에 어떤 의미가 붙어 있으면, 숫자를 그대로 쓰지 말고 일단 꼬리표를 붙여서 쓰게 되면 바탕글을 훨씬 읽기 쉽게 만들 수 있습니다. 바탕글 8 에서 "30"은 탄소 막대의 처음의 길이이고, "6.7e-6"은 탄소 막대의 확장 계수입니다. 따라서 여기에도 이름을 붙여서 바탕글을 쓰게 되면 글이 훨씬 읽기 쉽게 됩니다. 바탕글 9 를 보십시오.


바탕글 9::탄소막대 길이 구하기 (읽기 쉬운 바탕글)

| centigrade fahrenheit length initialLength coefficient |
centigrade := 500.                  "섭씨 온도"
coefficient := 6.7e-6.              "탄소막대의 확장 계수"
initialLength := 30.                "화씨 0도일 때의 처음 길이"
fahrenheit := 32 + (9/5 * centigrade).
length := initialLength * (1 + (coefficient * fahrenheit)). 
length.


바탕글 9 에는 탄소 막대의 처음 길이를 나타내는 'initialLength'와 확장 계수를 나타내는 'coefficient' 라는 두 개의 꼬리표를 사용했습니다. 그리고 간단한 풀이글을 달아서 각각의 꼬리표가 어떤 역할을 하는지를 좀 더 명확하게 나타냈습니다. 이와 같이 바탕글을 쓰게 되면 일 주일이 아니라 1년이 지난 뒤에 다시 바탕글을 보더라도 쉽게 바탕글을 이해할 수 있음은 물론, 탄소 막대가 아니라 황금 막대라 하더라도 확장 계수(coefficient)와 화씨 0 도일 때의 처음 길이(initialLength)만 알면 쉽게 결과를 얻을 수 있습니다.


1000 바이트의 예술


한 때 컴퓨터 잡지에서 "1000 바이트의 예술"이라는 제목으로 바탕글 쓰기 경진대회를 한 적이 있습니다. 이 대회에서는 어떤 문제가 주어지고, 그 주어진 문제를 바탕글로 풀어서 써야 하는데, 이 때 절대로 1000 바이트가 넘으면 안 된다는 규칙이 있습니다. 보통의 경우 2000~3000 바이트 정도가 되어야만 풀 수 있는 문제들이지만, 프로그래밍 언어가 가지고 있는 기교란 기교를 다 부려서 바탕글을 1000 바이트가 안 되게 쓰는 사람이 있습니다. 이를 보고 "와, 정말 예술이다"라고 감탄한 적이 있습니다. 그러나 이것은 어디까지나 즐기기 위한 것일 뿐, 이렇게 쓰여진 바탕글은 보통 사람들은 도저히 읽을 수 없으며, 고치기도 어렵습니다.


우리 나라에 C언어가 처음 보급되기 시작했을 때 사람들은 읽기 어려운 바탕글을 쓰는 프로그래머가 실력이 있다는 잘못된 생각을 가지고 있었습니다. 그래서 사람들은 갖은 방법을 동원해서 바탕글을 읽기 어렵게 만들고, 이것을 해설하면서 희열을 느꼈습니다. 물론 이러한 기교를 넣은 바탕글은 실행 효율이 좋을지는 모르겠지만 앞서 예로 든 것과 같이 전혀 읽을 수 없는 바탕글이 되고 맙니다. 심지어는 바탕글을 쓴 자신 조차도 며칠이 지나면 그 바탕글을 알기 어렵게 되는 경우가 많습니다.


바탕글을 어렵게 쓰는 것은 자만입니다. 자신의 생각을 있는 그대로 표현하는 것이 바로 진정한 프로그래머입니다. 요즘은 헝가리언 표기법을 많이 사용하는데, 이것은 보기 좋은 바탕글을 만들기 위한 방법입니다. 이름을 지을 때에도 최대한 읽기 쉽게 짓고, 글을 쓸 때도 최대한 읽기 쉽게 씁니다. 바로 이러한 경향은 바탕글은 읽기 쉬워야 한다는 것을 보여주는 좋은 보기라 할 수 있겠습니다.


성공의 비결은 실패

김 군은 집으로 돌아오면서 자신을 반성하고 있었습니다.

'수업이나 잘 들을걸...'
'프로그래밍이나 열심히 공부할 걸...'


지나 온 세월들이 후회됩니다. 그렇게 자책하며 집으로 돌아온 김 군은 컴퓨터를 켰습니다. 문득 편지함에 들어 있는 메일 한 통.


"힘 내! 실패는 누구나 다 하는 법이야. 우리가 술 마시고 있을 때 팀장은 또 밤을 세나봐. 자기가 만든 프로그램에서 오류를 찾고 있다나? 피히히!"


친구의 메일이었습니다. 따스한 격려의 말을 전해 준 친구가 김 군은 너무나 고마웠습니다. 다음부터는 좀 더 열심히 공부해야겠다고 생각하면서 김 군은 잠을 청합니다. 10년이 넘게 프로그램만 짜 온 팀장도 실수를 할 수 있다는 것을 위로로 삼으면서....


그렇습니다. 실수는 누구나 하는 법입니다. 어떤 일을 처리하다가 한 번 실수를 하게 되면 그 일은 머리에 오래도록 남습니다. 따라서 똑같은 실수를 할 확률은 점점 적어집니다. 또한 그렇게 실수를 함으로써 실력이 느는 것입니다. 흔히 우리는 전문적인 프로그래머들은 실수를 전혀 하지 않는다고 생각하기 쉽습니다. 그러나 이러한 전문 프로그래머들도 똑같이 실수를 합니다. 프로그래밍을 직업으로 하는 사람들은 밤을 세워가면서 모니터 앞에서 프로그램에 숨어 있는 벌레(bug)를 찾아내려고 애를 씁니다. 사람은 완전하지 않기 때문에 사람이 만들어내는 프로그램 역시 벌레를 품고 있습니다.훌륭한 프로그래머는 실수를 하지 않는 것이 아닙니다. 오히려 훌륭한 프로그래머란 프로그램에 숨어 있는 벌레를 잘 찾아내고 해결할 수 있는 사람을 말한다고 필자는 생각합니다.


프로그램에 숨어 있는 논리적인 잘못을 '벌레'(bug)라고 부르고, 이러한 논리적인 잘못, 즉 벌레를 없애는 작업을 "벌래 잡기"(debugging)이라고 합니다. 아무튼 이제 막 Smalltalk를 공부하는 여러분이나 전문적으로 프로그래밍을 작성하는 사람이나 똑같이 벌레를 만들어내고, 이러한 벌레를 잡기 위해서 애씁니다. 그러면 벌레를 잘 잡으려면 어떻게 해야 할까요? 왕도가 없습니다. 그저 많은 벌레를 잡아보는 수밖에는 다른 길이 없다는 말입니다. 앞으로 여러분들이 Smalltalk로 프로그래밍을 하다 보면 분명히 많은 벌레와 맞닥뜨리게 될 것입니다. 그러나 포기하지 마십시오. 벌레를 발견했다면 여러분의 실력이 오를 수 있는 기회라고 생각하십시오. 그리고 포기하지 말고 끝까지 벌레를 찾아내어 박멸해야겠다는 생각을 가지십시오. 여러분이 벌레와의 치열한 전투를 하다 보면 어느새 여러분도 모르게 프로그래밍 실력이 올라 있는 것을 발견하게 될 것입니다. Smalltalk에서는 언제든지 객체와 대화할 수 있고, 벌레를 잡기 위해 필요한 여러 가지 도구들―탐색기(inspector), 벌레잡이(debugger)―이 마련되어 있으므로 여러분의 벌레 사냥을 도와 줄 것입니다.


이번 마디에서 우리는 Smalltalk에서 프로그램을 작성할 때 어떤 실수를 범할 수 있으며, 이런 실수들을 어떻게 고칠 수 있는지에 대해서 알아보았습니다. 그리고 읽기 쉬운 바탕글을 작성하려면 어떻게 해야하는지에 대해서도 살펴보았습니다. 이 마디를 통해서 여러분은 바탕글을 쓰는 데 필요한 중요한 것을 배울 수 있었으리라 생각합니다.


다음 마디부터는 다시 Smalltalk의 객체 탐험이 시작됩니다. 지금까지는 프로그래머와 객체가 대화를 하는 방법만 설명했지만, 다음 마디부터는 프로그램을 사용하는 사람들과 프로그램 자체가 어떻게 대화를 할 수 있는지에 대해서 알아보도록 하겠습니다. 기대하셔도 좋습니다. ^^:


대화의 창

Smalltalk는 대화형 시스템입니다. 사용자가 컴퓨터에게 어떤 명령을 내리면 컴퓨터는 지체 없이 그 명령을 실행하고 반응을 나타냅니다. 우리가 지금까지 여러 가지 객체에게 지시(message)를 내리면 객체는 자기가 알아들을 수 있는 지시의 경우에는, 다시 말해서 객체가 지시를 어떻게 처리할 지에 대한 길수(method)를 알고 있다면 그 길수대로 행동하고, 지시를 알아들을 수 없을 경우에는 발자취 창(walkback window)을 표시했습니다. 이와 같은 환경은 Smalltalk를 공부하는 데에도 매우 편리하고, 실제로 큰 프로그램을 만들 때도 매우 큰 도움이 됩니다.


그런데 이러한 동작은 모두 프로그램을 만드는 프로그래머와 Smalltalk 사이의 대화입니다. 프로그래머는 자신이 만든 프로그램을 쓰는 사용자들과 프로그램이 어떻게 대화해야 할지를 정해줄 필요가 있습니다. Windows와 같은 대부분의 그림 사용자 환경(GUI)에서는 대화 상자를 사용하여 필요한 정보를 프로그램에 입력하고 알림 상자(message box)를 사용하여 프로그램의 여러 가지 상태를 출력합니다. 우리가 Smalltalk에서 보는 것과 같이 어떤 경우에는 복잡한 모양을 가지는 대화 상자와 알림 상자가 필요할 수도 있지만, 대부분은 간단한 글줄(string)을 입력할 수 있는 정도의 대화 상자와 프로그램의 간단한 상태를 사용자에게 알려줄 수 있는 알림 상자가 있으면 충분할 것입니다.


대화 상자와 알림 상자를 만들기 위해서는 보임새잡이(View Composer)를 사용해서 창 위에 여러 가지 연모를 배치해야 합니다. 그러나 간단한 값을 입력받거나 출력할 때마다 창을 그리고 연모를 배치하고, 이 연모를 조절하기 위해서 여러 가지 객체를 만드는 일은 번거롭기 때문에 Smalltalk에서는 간단한 대화 상자와 알림 상자를 미리 만들어 놓고 필요할 때마다 사용할 수 있도록 하고 있습니다. 이제 이 마디에서 우리는 Smalltalk에서 미리 제공하는 알림 상자와 값을 입력하거나 선택할 수 있는 대화 상자를 어떻게 사용하는지에 대해서 알아볼 것입니다. 이 마디의 내용을 모두 이해하고 나면 여러분은 이제 단순히 바탕글에 있는 내용만 가지고 실행되는 프로그램이 아니라 사용자가 입력한 자료에 근거해서 실행되는 융통성 있는 프로그램을 만들 수 있게 될 것입니다. 그럼 먼저 알림 상자부터 시작합시다.


알림 상자

여러분이 Windows를 비롯한 갖가지 프로그램을 사용하다보면 프로그램에서 일어나는 여러 가지 상황을 여러분에게 알려주기 위해서 작은 상자에 몇 줄의 글을 담아서 화면에 표시할 때가 있습니다.


그림 3-24::"내 컴퓨터" 창이 표시한 알림 상자 (Stp3-24.gif)


그림 3-24 를 보면 "내 컴퓨터" 창을 열고 디스켓을 넣지 않은 상태에서 A: 드라이브를 두 번 눌렀을 때 나타난 알림 상자를 관찰할 수 있습니다. 여러분은 얼마나 이런 실수를 자주 하는지는 모르겠지만 필자는 머리가 좀 나쁜 관계로 위와 같은 실수를 상당히 자주 합니다. :) 아무튼 위와 같이 여러 가지 프로그램을 사용하면서 사용자에게 알려야 할 사항이 있다면 알림 상자(message box)에 내용을 담아서 나타내고 사용자의 확인 과정을 거치게 됩니다.


Smalltalk는 미리 대화 상자를 객체로 포장해 놓고 여러분을 기다리고 있습니다. 우리가 "3.1" 마디에서 알림판(transcript)에 글을 쓸 때(#22) 어떤 객체를 사용했습니까? 네, 바로 Transcript였습니다. 그래서 우리가

Transcript nextPutAll: '안녕하세요?'


와 같은 글토막을 가늠하면 알림판에 글줄을 써 낼 수 있었습니다. 이것은 알림판이 객체이기 때문입니다. Smalltalk에서는 모든 것이...? 그렇죠! 객체입니다. 그렇다면 우리가 알아볼 알림 상자 역시...? 네, 그렇지요! 바로 객체입니다.


우리가 알림 상자에게 지시를 내리기 위해서는 Smalltalk에서 알림 상자를 어떻게 부르는지를 알아야 합니다. 다시 말해서 우리는 알림 상자의 이름을 알아야 한다는 것입니다.


Smalltalk에서 알림 상자는 "MessageBox"라는 이름이 붙어 있다.


바로 위의 문장이 답입니다. Smalltalk에서 알림 상자에 어떤 지시를 내리고 싶을 때에는 위에서 설명한 것처럼 "MessageBox"라는 이름을 사용합니다. 이름의 첫 글자가 대문자인 것을 보니깐 위의 이름은 분명히 큰 꼬리표(global variable)라는 것을 추측할 수 있었다면 여러분은 지금까지 제 글을 꼼꼼히 읽어오셨다고 말할 수 있겠습니다.(저것이 왜 큰 꼬리표인지 모르겠다면 "3.2"(#24) 마디로 되돌아가서 다시 한번 복습을 하는 것이 좋을 것 같습니다. ^^:)


아무튼 MessageBox가 알림상자를 나타낸다고 했으니, 이 이름을 일터에서 그대로 펴 봅시다. 먼저 새로운 일터를 하나 열고 깨끗한 일터에서 아래의 글토막을 펴 봅시다. 그러면 어떤 결과가 나타날까요?

MessageBox       MessageBox


"MessageBox"를 입력하고 Ctrl-D} 글쇠를 눌러서 위의 글토막을 펴봤더니 어떻게 됐습니까? 아무런 변화가 없이 "MessageBox"라는 글자가 그대로 나타났습니다. 그럼 "Transcript" 의 경우는 어떠했습니까? 기억이 나지 않는다면 직접 실험을 해 봅시다.

Transcript       a TranscriptShell


"Transcript"라는 이름에는 "a TranscriptShell" 이라는 객체가 붙어있었습니다. 그런데 "MessageBox" 꼬리표는 펴 봤자 그대로 "MessageBox"입니다. 따라서 "MessageBox"는 갈래(class)의 이름이라는 것을 알 수 있습니다.


우리는 Integer 갈래에게 #superclass 라는 지시를 내려서 "네 윗갈래가 뭐냐"고 물어볼 수 있었습니다. 만약 MessageBox 가 갈래의 이름이라면 역시 #superclass 지시를 알아들어야 할 것입니다. 그런지 안 그런지 일터에서 실험을 해 봅시다. 아래 글토막을 가늠해서 펴 보십시오.

MessageBox superclass    Object


여러분이 보시다시피 MessageBox의 윗갈래는 "Object"라는 것을 알 수 있습니다.


우리가 바로값이 아닌 객체를 만들어 내기 위해서는 어떻게 했는지 기억하십니까? "3.3" 에서 운동장을 가지고 놀았을 때 어떻게 했습니까? 네, 갈래에게 #new 라는 지시를 내려주었습니다. #new는 "새로운 객체를 만들어 내라"는 뜻이기 때문에 MessageBox 에 #new 라는 지시를 내려서 알림 상자를 만들 수 있습니다. 그렇지만 알림 상자는 한 번 만들어지고 사용자가 내용을 확인한 다음에는 쓸모가 없는 객체이기 때문에 일일이 #new 지시를 사용해서 새로 객체를 만들지 않고 간단한 지시를 사용해서 객체를 만들어낼 수 있도록 몇 가지의 지시가 준비되어 있습니다. 마치 우리가 색깔을 나타낼 때 "Color" 갈래에게 #new 라는 지시를 내리지 않고 바로 "Color red"와 같은 지시를 내려서 객체를 얻어내는 것과 비슷하다고 보면 되겠습니다.


알림 상자를 어떻게 나타내는지를 알아보기 전에 먼저 알림 상자에는 어떤 종류가 있는지를 알아보도록 하겠습니다. 알림 상자는 크게 다음과 같은 네 가지의 종류로 나누어볼 수 있습니다.

  • 정보 상자(information box)
    • "i"자의 아이콘(icon)을 달고 나오는 상자입니다. 단순히 프로그램에서 일어난 상황을 사용자에게 전달하기 위해 쓰이는 평범한 상자입니다.
  • 경고 상자(warning box)
    • 노란색 삼각형에 "!"가 들어가 있는 상자입니다. 상황이 제법 심각해질 지도 모르기 때문에 주의를 기울이라는 뜻으로 나타납니다. 사용자에게 경고하는 경우에 이 상자를 사용합니다.
  • 오류 상자(error box)
    • 붉은색 동그라미에 "X"표시가 되어 있는 상자입니다. 프로그램에 오류가 발생했거나 상황 자체가 매우 심각하여 사용자가 대처해야할 때 사용합니다. 대부분 이 오류 상자가 나타나게 되면 매우 심각한 상황이기 때문에 사용자가 특별한 대처를 해야할 때 사용합니다.
  • 질문 상자(confirm box)
    • "?" 아이콘이 그려져 있는 상자입니다. 프로그램이 어떤 일을 하려고 할 때 사용자에게 확인을 받기 위해서 사용합니다. 다른 알림 상자와는 달리 "예", "아니오" 라는 두 개의 단추를 가지고 있어서, 사용자가 해당 상황을 받아들일 것인지, 거부할 것인지를 선택할 수 있습니다.


물론 알림 상자에 복잡한 지시를 내려서 경고 상자이면서 "예'와 "아니오" 단추를 가지고 있는 확인 상자의 기능을 하게 할 수도 있습니다. 그러나 우리는 여기서 일반적으로 쓰이는 알림 상자를 만들어 볼 것입니다.


일터에서 다음의 글토막을 입력하여 가늠해 봅시다.

MessageBox notify: '작업이 끝났습니다.' caption: '알림'


위의 바탕글을 가늠하면 다음 그림 3-25 와 같이 알림 상자가 나타날 것입니다.


그림 3-25::#notify:caption:으로 만든 정보 상자 (Stp3-25.gif)


알림 상자가 나타나면 "확인" 단추를 눌러서 닫을 수 있습니다. 신기하지 않습니까? 이렇게 MessageBox 갈래에게 #notify:caption: 지시를 내리면 정보 상자를 만들어서 표시해 줍니다. 여기서 #notify:caption: 은 두 개의 열쇠말(keyword)을 가지고 있는 '쇠마디 지시'(keyword message)인데 "notify:" 뒤에는 알려야 할 실제의 내용을 글줄로 적고, "caption:" 뒤에는 알림 상자의 제목을 글줄로 적으면 됩니다.


경고 상자도 만들어 볼까요?

MessageBox warning: '그렇게 나오면 곤란하죠.' caption: '경고'


그림 3-26::#warning:caption:으로 만든 경고 상자 (Stp3-26.gif)


그림 3-26 을 보면 경고 상자가 나타나 있음을 알 수 있습니다. 정보 상자와 마찬가지로 "warning:" 뒤에는 경고할 내용을 적고 "caption:" 뒤에는 알림 상자의 제목을 적어주면 됩니다. 오류 상자도 당근! 만들 수 있습니다.

MessageBox errorMsg: '잘못하셨습니다.' caption: '오류'


그림 3-27::#errorMsg:caption:으로 만든 오류 상자 (Stp3-27.gif)


아주 재미있지요? 이번에는 대화 상자의 한 종류로써 사용자에게 질문을 던지는 질문 상자를 만들어 보도록 하겠습니다.

MessageBox confirm: '계속할까요?' caption: '확인'


그림 3-28::#confirm:caption:으로 만든 질문 상자 (Stp3-28.gif)


다른 대화 상자들은 한결같이 "확인" 단추만 가지고 있었는데 #confirm: caption: 지시로 만든 질문 상자의 경우에는 "예"와 "아니오"라는 두 개의 단추를 가지고 있습니다.


정보 상자, 경고 상자, 오류 상자에는 단추가 하나만 붙어 있기 때문에 글토막의 결과는 그리 중요하지 않습니다. 그러나 실험을 해 본 것처럼 질문 상자의 경우는 "예"와 "아니오"라는 두 개의 단추가 있기 때문에 사용자가 어떤 단추를 눌렀는지를 알아야 할 필요가 있습니다. #confirm:caption: 지시를 내리게 되면 질문 상자가 나오고 사용자가 어떤 단추를 눌렀는지를 결과값으로 넘겨줍니다. 그러면 어떤 결과가 나오는지 아래 글토막을 가늠해서 펴 봅시다. (그냥 가늠하는 것이 아니라 Ctrl-D 를 눌러서 결과를 펴보라는 말입니다.)

MessageBox confirm: '계속할까요?' caption: '확인' 
         true          "예" 눌렀을 
         false         "아니오" 눌렀을 


MessageBox 객체에게―'갈래'도 엄연한 객체입니다― #confirm:caption: 지시를 내리게 되면 이와 같이 사용자가 "예"를 선택하면 "true"(참)를 넘기고, "아니오"를 선택하면 "false"(거짓)을 넘깁니다. 나중에 프로그램의 조건을 판단할 수 있는 지시를 배우게 될 텐데, 그 지시를 사용하게 되면 사용자의 선택에 따라서 프로그램의 흐름을 바꿀 수 있습니다. 아무튼 지금은 질문 상자에서 사용자가 어떤 단추를 눌렀는지를 결과값으로 얻을 수 있다는 정도만 알아두면 되겠습니다.


갈래(class)와 객체(object)의 관계


이쯤 되면 '갈래'와 '객체'를 혼동하시는 분이 분명히 있을지도 모르겠습니다. 지금 머리가 복잡한 사람들을 위해서 여기서 잠시 갈래와 객체에 대해서 교통정리를 좀 하고 넘어가는 것이 좋겠습니다.


갈래(class)는 비슷한 속성을 갖는 객체들을 한데 묶을 때 사용합니다. 즉 "갈래"는 객체의 종류입니다. 1, 2, 3, -1, -2 등은 객체인데 이 객체는 모두 "정수"(Integer)라는 갈래에 속해 있습니다. '아버지', '어머니' 등은 모두 글줄(String) 갈래에 속해 있습니다. 따라서 모든 객체는 저마다 나름대로의 갈래에 딸려 있습니다. 어떤 갈래가 있을 때 그 갈래에 속하는 객체들을 "실체"(instance)라고 부른다는 것은 이미 "2.2.3"(#8) 마디에서 다룬 적이 있습니다. 따라서 갈래와 실체는 서로 반대되는 개념이라고 생각해도 무리가 없을 듯 합니다.


그런데 Smalltalk 에서는 모든 것이 객체입니다. 너무 지겹습니까? 그래도 이것이 Smalltalk의 기본 법칙이고 아주 중요하기 때문에 필자가 자꾸 말을 하는 것입니다. Smalltalk에서는 모든 것이 객체이기 때문에 심지어 갈래(class)도 또한 객체(object)입니다. 그것은 다음과 같은 글토막을 가늠해 봐도 알 수 있습니다.

(3 class) isKindOf: Object       true


위의 글토막에서 "3"에게 #class 지시를 내리면 "SmallInteger"(작은 정수)라는 이름의 갈래를 얻게 됩니다. 결국 "SmallInteger" 갈래에게 #isKindOf: 라는 지시가 보내지게 되는데, 이 말은 "SmallInteger가 객체인가"를 묻는 것입니다. Smalltalk가 뭐라고 대답했습니까? 분명히 '맞다'(tru)고 대답했습니다. 따라서 Smalltalk의 모든 갈래도 결국에는 객체인 것입니다.


그런데 갈래도 객체이기 때문에 이 갈래에도 지시를 내릴 수 있습니다. 그래서 실체가 알아들을 수 있는 지시를 '실체 지시'(instance message)라 하고 갈래가 알아들을 수 있는 지시를 '갈래 지시'(class message)라고 부르는 것입니다.


우리의 예에서 대화 상자를 만들어 내기 위해서 썼던 #notify:caption, #warning:caption:이나 새로운 객체를 만들어 내기 위해서 썼던 #new, 윗갈래를 알아내기 위해서 사용했던 #superclass 등은 모두 갈래가 알아들을 수 있는 갈래 지시인 것입니다. 반면 #+, #- 와 같은 겹마디 지시나 #size, #at:, #between:and: 등은 모두 실체에 보내는 지시이기 때문에 실체 지시가 되는 것입니다.


바로값(literal)이 아닌 객체를 새로 만들기 위해서 우리는 보통 그 객체가 속한 갈래에게 지시를 내립니다. 보통은 #new 를 사용하지만 때에 따라서는 다른 지시를 사용할 수도 있는데, 지금 보기로 들고 있는 MessageBox 에는 #notify:caption: 등의 지시로 새로운 알림 상자를 만들어낼 수 있으며, Color 갈래에는 #red, #green, #blue 등의 지시를 내려서 새로운 색깔 객체를 만들어낼 수 있습니다. 결국 이것은 갈래에게 실체 하나를 만들어 내라는 지시인 것이지요.


그렇다면 여기서 한 가지 여러분들이 궁금해 하는 점이 있을 것입니다. 모든 객체는 저마다의 갈래에 속해 있다면,


"갈래는 어떤 갈래에 속해 있는가"


하는 질문입니다. 다음과 같은 글토막을 펴 봅시다.

Integer class    Integer class


"Integer"는 정수를 나타내는 갈래입니다. 그런데 이 갈래에게 #class 지시를 내려서 어떤 결과가 나오는지 펴 보니깐 그대로 "Integer class"라는 결과를 넘겼습니다.


모든 갈래는 "풀이 갈래"(metaclass)에 속해있습니다. "풀이 갈래" 는 "갈래의 갈래"라고 할 수 있습니다. 즉 어떤 갈래가 있다면, 그 갈래는 "풀이 갈래" 의 실체(the instance of class of class = the instance of metaclass)인 것입니다. 풀이 갈래는 갈래를 풀어주는 역할을 합니다. 그러니까 "Integer"라는 갈래가 어떤 행동을 하려 할 때에는 "Integer class"라는, 즉 "Integer" 갈래의 풀이 갈래가 그 지시를 받아서 처리합니다. 여러분이 풀이 갈래에 대해서 이해하려면 아직도 상당히 많은 시간이 필요할 것입니다. 특히 C++, Java, Object Pascal 처럼 순수한 객체지향 언어가 아닌 기존의 언어를 알고 있는 사람이라면 거의 머릿속에서 폭풍이 휘몰아칠 것입니다. 아무튼 모든 갈래는 "풀이 갈래"에 속해있다고 생각합시다. 나중에 여러분이 갈래와 실체, 그리고 객체의 개념에 대해서 익숙해졌을 때 풀이 갈래에 대해서 좀 더 깊이 설명을 할 수 있을 것입니다. 결국에는 닭이 먼저냐, 알이 먼저냐는 논쟁과 비슷해질 수 있는데, Smalltalk는 이러한 논리를 아주 묘하게 이용하고 있습니다.


결론은

  • 갈래도 객체이므로 지시를 받을 수 있다.
  • 갈래와 반대되는 말은 '객체'가 아니라 __실체__이다.


위의 두 가지를 이해하고 있으면 헷갈리지 않을 것입니다. 갈래의 반대는 절대로 객체가 아닙니다.


지금까지 알아본 것을 정리하면 다음과 같습니다.


MessageBox가 알아들을 수 있는 갈래 지시

  • MessageBox notify: promptString caption: titleString
    • 창의 제목을 "titleString"으로 붙이고 "promptString"의 내용을 표시하는 "정보 상자"(information box)를 만들어 화면에 표시한다.
  • MessageBox warning: promptString caption: titleString
    • 창의 제목을 "titleString"으로 붙이고 "promptString"의 내용을 표시하는 "경고 상자"(warning box)를 만들어 화면에 표시한다.
  • MessageBox errorMsg: promptString caption: titleString
    • 창의 제목을 "titleString"으로 붙이고 "promptString"의 내용을 표시하는 "오류 상자"(error box)를 만들어 화면에 표시한다.
  • MessageBox confirm: promptString caption: titleString
    • 창의 제목을 "titleString"으로 붙이고 "promptString"의 내용을 표시하는 "질문 상자"(confirm box)를 만들어 화면에 표시한다. "예"와 "아니오" 단추를 함께 가지고 있고, 사용자가 누른 단추가 "예"이면 '참'(true)을, "아니오"이면 '거짓'(false)을 넘긴다.


지금까지 알림 상자에 여러분이 원하는 내용을 출력하는 방법을 알아보았습니다. 많이 연습해서 알림 상자를 쓰는데 익숙해지도록 합시다. 다음에는 사용자에게서 글줄을 입력받을 수 있는 글줄 입력 상자에 대해서 알아봅시다.


글줄 입력 상자

앞 나눔에서 우리는 컴퓨터의 상태를 나타낼 수 있는 알림 상자(message box)를 Smalltalk에서 어떻게 다루는지에 대해서 알아보았습니다. 이제 지금부터는 프로그램을 사용하는 사람들이 자신이 원하는 값을 입력할 수 있는 입력 상자에 대해서 알아볼 것입니다.


Smalltalk에서 기본적으로 제공하는 입력상자는 크게 두 가지의 종류가 있는데, 간단한 글줄을 입력할 수 있는 "글줄 입력 상자"(string prompter)와 미리 주어진 몇 개의 목록 중에서 한 개 이상을 선택할 수 있는 "목록 입력 상자"(list prompter)가 그것입니다. 먼저 글줄 입력 상자부터 알아봅시다.


알림 상자에는 "MessageBox"라는 이름이 붙어있었습니다. 우리는 새로운 알림 상자를 만들기 위해서 #notify:caption: #warning:caption: #errorMsg: caption #confirm:caption: 과 같은 네 개의 지시를 사용했습니다. 따라서 글줄 입력 상자를 다룰 때에도 먼저 이 객체에 어떤 이름이 붙어있는지를 알아야 할 필요가 있습니다.


글줄 입력 상자의 이름은 "Prompter"입니다.


즉 글줄 입력 상자에게 지시를 내리기 위해서는 "Prompter"라는 이름을 사용해야 한다는 것입니다. 그러면 알림 상자를 관찰했을 때와 마찬가지로 "Prompter"도 똑같은 방법으로 관찰을 해 봅시다.

Prompter         Prompter


일터에서 위의 글토막을 입력해서 펴 보면(display it) "Prompter"라는 이름이 그대로 나타납니다. 이는 "Prompter"가 단순한 객체의 이름이 아니라 갈래(class)의 이름일지도 모른다는 말입니다. Prompter 가 갈래라면 #superclass 지시를 알아들을 수 있을 것입니다.

Prompter superclass      ValueDialog


네, 우리의 생각과 같이 Prompter는 #superclass 지시를 알아들었고, 자신이 ValueDialog(값 대화 상자)의 아랫갈래라고 대답했습니다. 값 대화 상자(ValueDialog)는 프로그램에서 어떤 값을 입력받을 때 사용할 수 있는 대화 상자들의 갈래입니다. 지금 우리가 공부하고 있는 글줄 입력 상자도 프로그램에서 글줄을 입력받는데 사용되기 때문에 ValueDialog의 아랫갈래입니다.그럼 Prompter의 씻줄(hierarchy)을 계속해서 짚어 올라가 봅시다.

ValueDialog superclass           Dialog
Dialog superclass                Shell
Shell superclass                 CompositePresenter
CompositePresenter superclass    Presenter
Presenter superclass             Object
Object superclass                nil


바로 앞 나눔에서의 "MessageBox"보다 훨씬 많은 갈래들이 씻줄로 얽혀 있습니다. ValueDialog의 윗갈래는 전체 대화상자를 나타내는 "Dialog", 그 윗갈래는 응용 프로그램의 겉모양인 "Shell"이 있습니다. 계속해서 씻줄을 따라 올라가다 보면 차례대로 "CompositePresenter"(모임 풀이꾼)와 "Presenter"(풀이꾼)을 지나 마침내는 모든 객체들의 뿌리인 "Object"에 이어집니다.


위에서 살펴본 바와 같이 Dolphin Smalltalk의 경우 "Prompter"는 응용 프로그램의 뼈대인 "MVP"(Model-View-Presenter) 구조로 되어 있습니다. 풀이꾼(presenter)은 응용 프로그램의 모형(model)과 보임새(view)를 조정하여 전체 응용 프로그램을 통제하는 역할을 합니다. 아무튼 "Prompter" 역시 이러한 "MVP"의 구성 요소인 풀이꾼(presenter)의 일부라는 것만 생각하면 될 것입니다. 여기서 우리는 "Prompter"가 글줄 입력 상자라는 것만 알아두면 되고, 그 갈래가 얽혀있는 갈래들이 무슨 의미가 있는지는 자세히 알 필요가 없습니다. 그냥 "Prompter"는 풀이꾼의 한 종류라고만 생각하면 될 것입니다.


MessagBox 갈래에 우리는 갈래 지시(class message)를 내려서 알림 상자를 만든 것처럼 "Prompter"에도 그 갈래 자신이 알아들을 수 있는 지시를 내려서 글줄 입력상자를 만들어야 합니다. 일터에서 아래 글토막을 입력하고 가늠해(evaluate it) 보십시오.

Prompter prompt: '이름을 입력하세요' caption: '이름 입력'


그림 3-29::#prompt:caption:으로 표시한 글줄 입력 상자 (Stp3-29.gif)


위의 글토막을 입력해서 가늠해 보면 그림 3-29 와 같이 글줄 입력 상자가 나타날 것입니다. 창이 하나 열리고 글줄 입력칸(edit box) 안에서 여러분의 입력을 기다리는 커서가 깜박거리고 있습니다. 이제 여기에 여러분의 이름을 넣고 -Enter 글쇠를 눌러봅시다. 저는 "김찬홍"이라고 입력하겠습니다.


이름을 입력하고 -Enter 를 눌렀더니 어떻게 되었습니까? 네, 상자가 닫혔습니다. 그런데 일터를 보면 아무런 결과가 나타나지 않았습니다. 도대체 어떻게 된 일일까요?


Prompter 갈래에 #prompt:caption: 이라는 지시를 내리면 그림 3-29 와 같은 글줄 입력 상자를 표시합니다. 이제 여기에서 사용자가 어떤 글줄을 입력하고 -Enter 를 누르면 상자는 닫히고 #prompt:caption: 지시의 실행을 마칩니다. 이 때 결과로써 사용자가 입력한 글줄이 넘겨집니다. Smalltalk에서 어떤 객체에게 지시를 내리면 __반드시 결과가 되돌아온다는 것__ 을 기억하십니까? 우리의 글줄 입력 상자도 사용자의 입력에 반응하여 지시의 결과를 넘깁니다. 그런데 우리는 위의 글토막을 가늠하기만 했지 그 결과를 펴지 않았기 때문에 아무런 값도 나타나지 않은 것입니다.


자, 이제 다음의 글토막을 펴 봅시다. 아래의 글토막을 Ctrl-D 글쇠를 이용해서 펴 보십시오.

Prompter prompt: '이름을 입력하세요' caption: '이름 입력'  '김찬홍'


위에서 한 것처럼 Prompter 갈래에 다시 한 번 #prompt:caption: 지시를 내렸습니다. 입력 상자가 나타나면 여러분 자신의 이름을 입력한 뒤에 -Enter 글쇠를 누릅니다. 상자가 닫히면서 여러분이 입력한 값이 일터에 표시되어 있는 것을 확인할 수 있을 것입니다.


그림 3-30::#prompt:caption: 지시를 내린 결과를 편 모습 (Stp3-30.gif)


이렇게 Prompter 갈래에 #prompt:caption: 지시를 내려서 글줄 입력 상자를 열 수 있고 여기에 어떤 값을 입력하면 지시의 결과값으로 입력 받은 글줄을 넘기게 됩니다. 프로그래머는 이렇게 입력된 글줄을 이용해서 프로그램에 필요한 자료로 이용하면 되는 것입니다.


'가늠하기'와 '펴기'


혹시나 해서 정리해 둡니다. Smalltalk에서 글토막을 실행하는 방법은 세 가지가 있습니다. '가늠하기', '펴기', '탐색하기'가 바로 그것입니다. 이는 이미 "첫째 마당"에서 설명을 한 바 있습니다. 그러나 혼동되시는 분들을 위해서 다시 한 번 간단하게 정리해 둡니다.

  • 가늠하기(evaulate it): 글토막을 실행하기만 하는 동작. 글토막을 가늠한 뒤에 나온 결과는 버린다.
  • 펴기(display it): 글토막을 실행한 뒤에 실행 결과로 나오는 값을 표시하는 동작. 글토막의 실행 결과는 덩이가 씌워진 채로 나타나므로, 결과가 필요 없으면 -Delete 글쇠를 이용하여 지울 수 있다.
  • 탐색하기(inspect it): 글토막을 실행한 뒤에 실행 결과로 나오는 객체를 '객체 탐색기'(inspector)에서 들여다 보는 동작. 탐색기 안에 객체의 모든 상태가 표시된다.


글토막을 실행할 때에 "Workspace" 메뉴에 있는 "Display it", "Evaluate it", "Inspect it" 메뉴를 사용하면 각각의 글토막을 가늠하거나 펴거나 탐색할 수 있습니다. 단축키는 가늠하기가 Ctrl-E, 펴기가 Ctrl-D, 탐색하기가 Ctrl-I 입니다.


그런데 만약 #prompt:caption: 지시를 받아서 열린 입력 상자에 아무런 내용을 입력하지 않고 -Esc 글쇠나 "Cancel" 단추를 누르면 어떤 일이 일어날까요? 벌써 호기심 많은 사람들은 시험을 해 보았을 것입니다. 결론을 말한다면 "nil", 즉 '헛 값'이 넘어옵니다. 누차 이야기했지만 "nil"은 "아무 것도 없다"는 뜻을 나타낸다고 했습니다. 그러므로 사용자가 아무런 글줄도 입력하지 않은 상태에서 그냥 입력 상자를 닫아버렸다면 되돌아오는 값 역시 "헛"일 수 밖에는 없겠지요.


글줄 입력 상자는 #prompt:caption: 지시와 더불어 다음과 같은 갈래 지시 하나를 더 알아들을 수 있습니다. 아래 글토막을 펴 봅시다.

Prompter on: '김찬홍' prompt: '이름을 고치세요.' caption: '이름 수정'


위의 글줄을 펴면 다음 그림과 같은 창이 나타날 것입니다.


그림 3-31::이미 있는 글줄을 편집하기 (Stp3-31.gif)


위에서 사용된 #prompt:caption: 지시가 완전히 새로운 글줄을 만들어 내는 것과는 달리 #on:prompt:caption: 지시는 "on:" 뒤에 있는 글줄에 기초해서 입력 상자를 만듭니다. 그림에서 보는 바와 같이, "on:" 열쇠말 뒤에 있는 '김찬홍' 이라는 글줄이 입력칸에 표시되었습니다. 여러분은 이렇게 해서 표시된 글줄을 고쳐서 새로 입력할 수도 있겠고, 완전히 새로운 글줄을 다시 입력해도 될 것입니다. 아무튼 #on:prompt:caption:은 "on:" 뒤에 나타난 글줄을 편집하여 결과를 넘긴다는 점이 다릅니다. 물론 이렇게 해서 열린 입력 상자에서도 "Cancel" 단추나 -Esc 글쇠를 누르면 "nil" 을 넘기게 됩니다.


이제 Prompter 갈래가 알아들을 수 있는 지시에 대해서 모두 다 살펴보았습니다. 아래에 Prompter갈래가 알아들을 수 있는 갈래 지시를 다시 한 번 정리해 보았습니다.


Prompter 갈래가 알아들을 수 있는 지시

  • Prompter prompt: aStringPrompt caption: aStringCaption
    • 창의 제목을 "aStringCaption"으로 하는 글줄 입력 상자를 만들어 연다. 입력 상자 안에는 "aStringPrompt" 글줄로 지정된 내용이 표시된다. 입력 칸에 글줄을 써 넣고 "OK" 단추를 누르거나 <Enter> 글쇠를 치면 입력칸의 내용을 그대로 넘긴다. "Cancel" 단추나 Esc 글쇠를 치면 상자는 닫히고 "nil" 을 넘긴다.
  • Prompter on: aValueModel prompt: aStringPrompt caption: aStringCaption
    • "aStringCaption" 제목을 갖는 글줄 입력 상자를 만든다. 상자 안에는 "aStringPrompt" 글줄의 내용이 표시되며, 입력칸 안에는 "aValueModel"에 넣어준 객체(보통은 글줄)의 내용이 표시된다. 글줄을 편집하거나 새로 입력한 다음 "OK" 단추나 -Enter 글쇠를 치면 해당하는 글줄이 넘겨지고, "취소" 단추나 Esc 글쇠를 치면 "nil"이 넘어온다.


한글 단추로 바꿀 수 없을까?


글줄 입력 상자를 열어보면 "OK"와 "Cancel"이라는 두 개의 단추가 있습니다. 대화상자에서 보통 이 단추는 "확인"과 "취소"라고 표시됩니다. 그럼 우리의 Dolphin Smalltalk 에 있는 글줄 입력 상자의 단추는 한글로 고칠 수 없는 것일까요? 절대 그렇지 않습니다. 당연히 고칠 수 있습니다. 여러분이 MVP 구조에 대해서 좀 더 많이 익숙해지고, 보임새잡이(view composer)에 익숙해지게 되면 얼마든지 대화 상자를 구성하는 단추의 글을 바꿀 수 있습니다. 그러나 그 길은 아직 멀고도 멉니다.... ^^:


입력 상자의 첫 번째 종류로써 여기서는 "글줄 입력 상자"를 어떻게 만들고, 만들어진 글줄 입력 상자에 입력된 글줄을 어떻게 가져오는지에 대해서 살펴보았습니다. MessageBox 갈래와 같이 Prompter 갈래도 역시 갈래 자신이 알아들을 수 있는 지시를 통해서 입력 상자를 만들어서 보여주었습니다. 따라서 알림 상자나 입력 상자 모두 갈래 지시를 잘 활용해야 할 것입니다. 다음 나눔에서는 몇 가지의 선택할 수 있는 값을 준비하고 그 중에서 사용자가 원하는 것을 골라서 입력할 수 있는 목록 입력 상자에 대해서 알아보겠습니다.


목록 입력 상자

앞에서 우리는 입력 상자를 열어 글줄을 입력받는 방법을 알아보았습니다. Prompter 라는 갈래에 #prompt:caption:이나 #on:prompt:caption: 같은 지시를 내려서 글줄을 입력할 수 있었습니다. 그런데 프로그램을 작성하다 보면 글줄을 입력받는 것보다는 미리 몇 가지의 항목을 정해 놓고 사용자들에게 선택권을 주는 것이 더 유용할 때가 있습니다. 특히 글쇠판에 익숙하지 못한 사람들은 화면에 표시된 항목을 마우스로 클릭하는 것이 보다 더 쉬울 것입니다. 여기서는 미리 마련된 목록을 선택하여 값을 입력할 수 있는 목록 입력 상자에 대해서 알아봅시다.


목록 입력 상자에도 "ListPrompter"라는 이름이 붙어있습니다. 역시 이 "ListPrompter"는 갈래(class)의 이름입니다. 아래 글토막을 차례로 가늠하 면서 ListPrompter가 어떤 갈래 씻줄에 얽혀있는지 알아봅시다.

ListPrompter superclass          ValueDialog
ValueDialog superclass           Dialog
Dialog superclass                Shell
Shell superclass                 CompositePresenter
CompositePresenter superclass    Presenter
Presenter superclass             Object
Object superclass                nil


"ListPrompter" 갈래 역시 "Prompter"와 마찬가지로 "ValueDialog"(값 대화 상자)에 씻줄이 얽혀있습니다. 따라서 글줄 입력 상자와 목록 입력 상자는 둘 다 비슷한 특성을 가지고 있다고 생각하면 됩니다. 다만 글줄 입력 상자가 글쇠판에서 글자 입력을 받아서 처리하는 것과는 달리 목록 입력 상자의 경우는 미리 정해진 목록에서 선택하는 것이 다를 뿐입니다.


"ListPrompter" 갈래가 알아들을 수 있는 갈래 지시를 내림으로써 목록 입력 상자를 열고 값을 입력받을 수 있습니다. 일터에서 다음의 글토막을 펴 보십시오.

ListPrompter list: #('빨강' '파랑' '노랑') caption: '색깔을 고르세요.'  '노랑'


그림 3-32::#list:caption: 지시를 받은 ListPrompter (Stp3-32.gif)


위의 글토막에서처럼 ListPrompter에 #list:caption:이라는 지시를 내리면 목록 입력 상자를 열고 사용자의 선택을 기다립니다. 목록 입력 상자가 처음 열릴 때에는 항상 첫 번째 항목이 돋이되어 있습니다. 우리의 경우는 "빨강" 이 돋이되어 있을 것입니다. 저는 이 상태에서 "노랑"을 돋이한 다음 "확인" 단추나 Enter 글쇠를 눌렀습니다. 그랬더니 글토막의 실행 결과로 '노랑' 이라는 글줄이 그대로 넘어왔습니다.


위의 보기에서 사용자는 새로운 글줄을 입력할 필요가 없습니다. 다만 우리가 지정해 준 세 개의 항목, '빨강', '파랑', '노랑'만 선택할 수 있도록 되어 있습니다. 따라서 사용자가 선택한 것이 그대로 결과값으로 넘어오는 것입니다. 이와 같은 목록 입력 상자는 프로그램에서 어떤 상황에 직면했을 때 사용자에게 선택권을 줄 수 있기 때문에 매우 편리할 것입니다.


{{}}#list:caption:에서 "list:" 열쇠말(keyword)의 뒤에는 사용자가 어떤 항목을 선택할 수 있는지를 적어주어야 합니다. 보는바와 같이 "list:" 열쇠말 뒤에는 배열(array)을 써 주었습니다. 여기서 쓴 #( )은 "2.3.5."(#19) 마디에서 배열의 바로값(array literal)을 만들 때 사용한다고 설명한 바 있습니다. 아무튼 위의 경우 '빨강', '파랑', '노랑' 이라는 세 개의 성분을 가진 배열을 만들어서 "list:" 열쇠말 뒤에 놓아주었습니다. 이렇게 했기 때문에 목록 상자에 세 개의 항목이 포함되어 있는 것입니다. "caption:" 의 경우는 설명할 필요도 없이, 목록 입력 상자의 제목 표시줄에 써 낼 글줄을 적어주면 됩니다. 여기에는 사용자가 무엇을 선택해야 할지를 적어주면 좋을 것입니다.


"list:" 열쇠말 뒤에는 배열을 놓아줄 수 있다고 했습니다. 그런데 배열에는 글줄뿐만 아니라 필요하면 어떤 객체이든지 모두 다 들어갈 수 있습니다. 그래서 다음과 같이 할 수 있습니다. 바탕글 1 에 덩이(block)를 씌운 후 펴 봅시다.


바탕글 1::여러 가지 갈래의 객체 목록 선택하기

ListPrompter
    list: #(123 3.14 false true #apple nil $a '가나다')
    caption: '골라~ 골라~'.


글토막을 한 줄에 쓰기가 너무 길어질 경우에는 위와 같이 두 줄 이상으로 나누어 쓴 다음 덩이를 씌워서 가늠해도 상관이 없다는 것은 이제 여러분들도 잘 알고 있을 것입니다. 위와 같이 바탕글 1 을 펴면 다음 그림과 같은 목록 입력 상자가 열리게 됩니다.


그림 3-33::여러 가지 갈래를 담은 목록 입력 상자 (Stp3-33.gif)


그림에서 보면 정수, 소수, 거짓(false), 참(true), 이름값(#apple) 글자($a)와 글줄('가나다')이 나타난 것을 볼 수 있습니다. 이 목록 중에 여러분이 고르고 싶은 데로 항목을 고른 뒤에 "확인" 단추를 눌러 보면, 바탕글을 실행하고 난 뒤에 여러분이 선택한 목록이 그대로 나타나 있는 것을 볼 수 있습니다. 신기하지 않습니까? 이렇게 목록 입력 상자를 사용하면 여러 개의 객체를 목록으로 만들어 놓고, 사용자가 원하는 것을 고르게 할 수 있습니다.


이 목록 입력 상자에서 재미있는 것은, 헛(nil)의 경우는 항목에 'nil' 이라 표시되지 않고 아예 비어있는 항목이 만들어졌다는 것입니다. 왜냐하면 "nil" 은 아무 것도 없는 그야말로 '헛'을 나타내기 때문에 완전히 비어 있는 항목이 만들어지게 된 것입니다.


그런데 목록 입력 상자만을 봐서는 어떤 것이 글줄이고 어떤 것이 글자이며, 또한 어떤 것이 이름값인지를 구별하기가 어렵습니다. 그렇지만 대부분의 경우 목록 입력 상자에서는 숫자나 글줄만 사용하기 때문에 실제로 큰 혼란은 없을 것이라고 생각합니다.


지금까지 우리는 목록 상자에서 한 개의 항목만 선택할 수 있었습니다. 그런데 필요하면 두 개 이상의 항목을 선택할 수도 있습니다. 아래의 바탕글 3 을 일터에 입력한 다음 펴 보십시오.


바탕글 2""두 개 이상의 항목을 선택할 수 있는 목록 입력 상자

ListPrompter
    multipleSelectionList: #('빨강' '파랑' '노랑')
    caption: '색깔을 두 개 이상 고르세요.'


겉으로 언뜻 보면 ListPrompter에 #list:caption: 지시를 내려서 열린 목록 입력 상자나 #multipleSelectionList:caption: 으로 열린 목록 입력 상자는 별로 달라 보이지 않습니다. 그러나 항목을 선택할 때 다음과 같은 방법을 사용하면 두 개 이상의 항목을 선택할 수 있습니다.


목록 상자에서 두 개 이상의 항목 선택하기


  • 한 개의 목록을 선택하려면 그냥 해당 항목을 클릭한다.
  • 연속된 목록을 선택할 경우에는 시작 항목을 클릭한 후 끝 항목은 -Shift 키를 누른 상태에서 클릭한다.
  • 연속되지 않은 목록을 선택할 때에는 -Ctrl 키와 함께 누른다.


이렇게 하면 목록 입력 상자에 있는 항목을 두 개 이상 고를 수 있습니다. 그러면 우리도 위의 방법을 사용해서 두 개 이상의 항목을 골라보도록 하겠습니다. 다음과 같이 해 보십시오.

  • '빨강'을 한 번 클릭하여 선택합니다.
  • -Ctrl 키를 누른 상태에서 마우스로 '노랑'을 한 번 클릭합니다.


위와 같이 했다면 다음 그림과 같이 두 개의 항목이 선택된 것을 볼 수 있을 것입니다.


그림 3-34::다중 선택 목록 상자에서 항목 선택하기


위와 같이 "빨강"과 "노랑"을 함께 선택한 뒤에 "확인" 단추를 누르면 결과로 다음과 같은 값을 넘기는 것을 볼 수 있습니다.

 #('빨강' '노랑')


이와 같이 ListPrompter 갈래에 #multipleSelectionList:caption: 지시를 내리면 두 개 이상의 항목을 선택하여 결과를 얻을 수 있습니다. #list: caption: 지시를 사용할 때와 달라진 것은 넘어오는 결과가 배열이라는 것입니다. 왜냐하면 한꺼번에 두 개 이상의 객체를 담을 때에는 배열과 같이 여러 개의 객체를 담는 통(container)이 필요하기 때문입니다.


그럼 여기서 "바탕글 2" 를 한 번 더 펴 봅시다. 이번에 열린 상자에서는 "파랑"이라는 한 가지의 항목만을 선택한 다음 "확인" 단추를 눌러봅시다. 그러면 결과로 #('파랑')이 넘어오는 것을 볼 수 있을 것입니다. 이와 같이 한 개의 항목만 선택할 경우에도 #multipleSlectionList:caption: 지시의 결과는 언제나 배열이라는 것을 알 수 있습니다.


배열에 들어 있는 객체는 #at: 지시를 사용하면 언제든지 내용을 볼 수 있습니다. 다음 바탕글을 펴 보십시오.


바탕글 3::#multipleSelectionList:caption:의 결과값 활용

| result |

result := ListPrompter
    multipleSelectionList: #('빨강' '파랑' '노랑')
    caption: '색깔을 두 개 이상 고르세요.'.
result at: 2.


위의 바탕글을 펴면 목록 입력 상자가 열린 것입니다. 이 때 그림 3-34 와 같이 "빨강"과 "파알"을 선택한 다음 "확인" 단추를 눌러봅시다. 그러면 바탕글의 결과로 '파랑'이 나타날 것입니다. 위의 바탕글에서 result 꼬리표는 #multipleSelectionList:caption: 지시의 결과값으로 나온 배열 객체에 붙어 있을 것입니다. 위와 같이 '빨강'과 '파랑', 두 개의 항목을 선택헀다면 result 꼬리표의 첫 번쨰 원소는 '빨강'이, 두 번째 원소는 '파랑'이 될 것입니다. 따라서

result := #('빨강' '파랑').


과 같은 글토막을 실행한 것처럼 될 것입니다. 따라서 #at: 지시에서 두 번째 원소를 꺼내라고 지시했기 때문에 결과로 '파랑'이 나온 것입니다. 만약 여러분이 위의 바탕글을 폈을 떄 한 개의 항목만 선택했다면 어떻게 될까요? 이 경우에는 발자취 창이 열리면서 "Index 2 is out of bounds" 라는 예외가 발생할 것입니다. 왜냐하면 한 개의 항목만 선택할 경우에는 당연히 두 번째의 원소는 존재하지 않기 때문입니다.


이와 같이 #multipleSelectionList:caption: 의 결과값은 배열이기 때문에, 결과를 다룰 때에도 #at: 과 같이 배열 객체를 다룰 수 있는 지시를 사용해야 하는 것입니다.


지금까지 우리는 프로그램 사용자들에게서 글줄을 입력받을 수 있는 글줄 입력 상자와 이미 마련된 목록을 제시하고 그 중에서 선택하게 할 수 있는 목록 입력 상자에 대해서 알아보았습니다. 이제 이 두 가지 객체의 사용법을 익혔으니 다음에는 이것을 어디에 사용하는지에 대해서 실제로 알아보도록 하겠습니다.


Notes